blob: 653a12dd8a69078ba0a527290f4a11461224072f [file] [log] [blame]
// Copyright (c) 2012, 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.
import "package:expect/expect.dart";
import 'dart:async';
import "package:async_helper/async_helper.dart";
import 'dart:collection';
import "package:compiler/src/resolution/resolution.dart";
import "compiler_helper.dart";
import "parser_helper.dart";
import 'package:compiler/src/dart_types.dart';
import 'package:compiler/src/elements/modelx.dart';
import 'link_helper.dart';
Node buildIdentifier(String name) => new Identifier(scan(name));
Node buildInitialization(String name) =>
parseBodyCode('$name = 1',
(parser, tokens) => parser.parseOptionallyInitializedIdentifier(tokens));
createLocals(List variables) {
var locals = <Node>[];
for (final variable in variables) {
String name = variable[0];
bool init = variable[1];
if (init) {
locals.add(buildInitialization(name));
} else {
locals.add(buildIdentifier(name));
}
}
var definitions = new NodeList(null, LinkFromList(locals), null, null);
return new VariableDefinitions(null, Modifiers.EMPTY, definitions);
}
Future testLocals(List variables) {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
ResolutionResult result = visitor.visit(createLocals(variables));
Element element = result != null ? result.element : null;
// A VariableDefinitions does not have an element.
Expect.equals(null, element);
Expect.equals(variables.length, map(visitor).length);
for (final variable in variables) {
final name = variable[0];
Identifier id = buildIdentifier(name);
ResolutionResult result = visitor.visit(id);
final VariableElement variableElement =
result != null ? result.element : null;
MethodScope scope = visitor.scope;
Expect.equals(variableElement, scope.elements[name]);
}
return compiler;
});
}
main() {
asyncTest(() => Future.forEach([
testLocalsOne,
testLocalsTwo,
testLocalsThree,
testLocalsFour,
testLocalsFive,
testParametersOne,
testFor,
testTypeAnnotation,
testSuperclass,
// testVarSuperclass, // The parser crashes with 'class Foo extends var'.
// testOneInterface, // Generates unexpected error message.
// testTwoInterfaces, // Generates unexpected error message.
testFunctionExpression,
testNewExpression,
testTopLevelFields,
testClassHierarchy,
testEnumDeclaration,
testInitializers,
testThis,
testSuperCalls,
testSwitch,
testTypeVariables,
testToString,
testIndexedOperator,
testIncrementsAndDecrements,
testOverrideHashCodeCheck,
testSupertypeOrder,
testConstConstructorAndNonFinalFields,
], (f) => f()));
}
Future testSupertypeOrder() {
return Future.wait([
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""
class I1 {}
class I2 {}
class J1 extends K1 {}
class J2 implements K2 {}
class K1 {}
class K2 {}
class L1 {}
class A implements I1, I2 {}
class B extends A implements J1, J2 {}
class C extends B implements L1 {}
""");
compiler.resolveStatement("C c;");
ClassElement classA = compiler.mainApp.find("A");
ClassElement classB = compiler.mainApp.find("B");
ClassElement classC = compiler.mainApp.find("C");
Expect.equals('[ I2, I1, Object ]', classA.allSupertypes.toString());
Expect.equals('[ A, J2, J1, I2, I1, K2, K1, Object ]',
classB.allSupertypes.toString());
Expect.equals('[ B, L1, A, J2, J1, I2, I1, K2, K1, Object ]',
classC.allSupertypes.toString());
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""
class X<T> {}
class Foo extends X<Foo> {}
class Bar extends Foo implements X<Bar> {}
""");
compiler.resolveStatement("Bar bar;");
ClassElement classBar = compiler.mainApp.find("Bar");
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.MULTI_INHERITANCE,
compiler.errors[0].message.kind);
Expect.equals(0, compiler.crashes.length);
}),
]);
}
Future testTypeVariables() {
matchResolvedTypes(visitor, text, name, expectedElements) {
VariableDefinitions definition = parseStatement(text);
visitor.visit(definition.type);
InterfaceType type = visitor.registry.mapping.getType(definition.type);
Expect.equals(definition.type.typeArguments.slowLength(),
type.typeArguments.length);
int index = 0;
for (DartType argument in type.typeArguments) {
Expect.equals(true, index < expectedElements.length);
Expect.equals(expectedElements[index], argument.element);
index++;
}
Expect.equals(index, expectedElements.length);
}
return Future.wait([
MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
compiler.parseScript('class Foo<T, U> {}');
ClassElement foo = compiler.mainApp.find('Foo');
matchResolvedTypes(visitor, 'Foo<int, String> x;', 'Foo',
[compiler.intClass, compiler.stringClass]);
matchResolvedTypes(visitor, 'Foo<Foo, Foo> x;', 'Foo',
[foo, foo]);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript('class Foo<T, U> {}');
compiler.resolveStatement('Foo<notype, int> x;');
Expect.equals(1, compiler.warnings.length);
Expect.equals(MessageKind.CANNOT_RESOLVE_TYPE,
compiler.warnings[0].message.kind);
Expect.equals(0, compiler.errors.length);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript('class Foo<T, U> {}');
compiler.resolveStatement('var x = new Foo<notype, int>();');
Expect.equals(1, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
Expect.equals(MessageKind.CANNOT_RESOLVE_TYPE,
compiler.warnings[0].message.kind);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript('class Foo<T> {'
' Foo<T> t;'
' foo(Foo<T> f) {}'
' bar() { g(Foo<T> f) {}; g(); }'
'}');
ClassElement foo = compiler.mainApp.find('Foo');
foo.ensureResolved(compiler);
foo.lookupLocalMember('t').computeType(compiler);;
foo.lookupLocalMember('foo').computeType(compiler);;
compiler.resolver.resolve(foo.lookupLocalMember('bar'));
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
}),
]);
}
Future testSuperCalls() {
return MockCompiler.create((MockCompiler compiler) {
String script = """class A { foo() {} }
class B extends A { foo() => super.foo(); }""";
compiler.parseScript(script);
compiler.resolveStatement("B b;");
ClassElement classB = compiler.mainApp.find("B");
FunctionElement fooB = classB.lookupLocalMember("foo");
ClassElement classA = compiler.mainApp.find("A");
FunctionElement fooA = classA.lookupLocalMember("foo");
ResolverVisitor visitor =
new ResolverVisitor(compiler, fooB,
new ResolutionRegistry.internal(compiler,
new CollectingTreeElements(fooB)));
FunctionExpression node = (fooB as FunctionElementX).parseNode(compiler);
visitor.visit(node.body);
Map mapping = map(visitor);
Send superCall = node.body.asReturn().expression;
FunctionElement called = mapping[superCall];
Expect.isNotNull(called);
Expect.equals(fooA, called);
});
}
Future testSwitch() {
return MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo { foo() {"
"switch (null) { case '': break; case 2: break; } } }");
compiler.resolveStatement("Foo foo;");
ClassElement fooElement = compiler.mainApp.find("Foo");
FunctionElement funElement = fooElement.lookupLocalMember("foo");
compiler.processQueue(compiler.enqueuer.resolution, funElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL,
compiler.errors[0].message.kind);
Expect.equals(2, compiler.infos.length);
Expect.equals(MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE,
compiler.infos[0].message.kind);
Expect.equals(MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE,
compiler.infos[1].message.kind);
});
}
Future testThis() {
return Future.wait([
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo { foo() { return this; } }");
compiler.resolveStatement("Foo foo;");
ClassElement fooElement = compiler.mainApp.find("Foo");
FunctionElement funElement = fooElement.lookupLocalMember("foo");
ResolverVisitor visitor =
new ResolverVisitor(compiler, funElement,
new ResolutionRegistry.internal(compiler,
new CollectingTreeElements(funElement)));
FunctionExpression function =
(funElement as FunctionElementX).parseNode(compiler);
visitor.visit(function.body);
Map mapping = map(visitor);
List<Element> values = mapping.values.toList();
Expect.equals(0, mapping.length);
Expect.equals(0, compiler.warnings.length);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.resolveStatement("main() { return this; }");
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.NO_INSTANCE_AVAILABLE,
compiler.errors[0].message.kind);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo { static foo() { return this; } }");
compiler.resolveStatement("Foo foo;");
ClassElement fooElement = compiler.mainApp.find("Foo");
FunctionElement funElement = fooElement.lookupLocalMember("foo");
ResolverVisitor visitor = new ResolverVisitor(compiler, funElement,
new ResolutionRegistry.internal(compiler,
new CollectingTreeElements(funElement)));
FunctionExpression function =
(funElement as FunctionElementX).parseNode(compiler);
visitor.visit(function.body);
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.NO_INSTANCE_AVAILABLE,
compiler.errors[0].message.kind);
}),
]);
}
Future testLocalsOne() {
return Future.forEach([
() => testLocals([["foo", false]]),
() => testLocals([["foo", false], ["bar", false]]),
() => testLocals([["foo", false], ["bar", false], ["foobar", false]]),
() => testLocals([["foo", true]]),
() => testLocals([["foo", false], ["bar", true]]),
() => testLocals([["foo", true], ["bar", true]]),
() => testLocals([["foo", false], ["bar", false], ["foobar", true]]),
() => testLocals([["foo", false], ["bar", true], ["foobar", true]]),
() => testLocals([["foo", true], ["bar", true], ["foobar", true]]),
() => testLocals([["foo", false], ["foo", false]])
.then((MockCompiler compiler) {
Expect.equals(1, compiler.errors.length);
Expect.equals(
new Message(MessageKind.DUPLICATE_DEFINITION, {'name': 'foo'}, false),
compiler.errors[0].message);
})], (f) => f());
}
Future testLocalsTwo() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
Node tree = parseStatement("if (true) { var a = 1; var b = 2; }");
ResolutionResult element = visitor.visit(tree);
Expect.equals(null, element);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(2, map(visitor).length);
List<Element> elements = new List<Element>.from(map(visitor).values);
Expect.notEquals(elements[0], elements[1]);
});
}
Future testLocalsThree() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
Node tree = parseStatement("{ var a = 1; if (true) { a; } }");
ResolutionResult element = visitor.visit(tree);
Expect.equals(null, element);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(3, map(visitor).length);
List<Element> elements = map(visitor).values.toList();
Expect.equals(elements[0], elements[1]);
});
}
Future testLocalsFour() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
Node tree = parseStatement("{ var a = 1; if (true) { var a = 1; } }");
ResolutionResult element = visitor.visit(tree);
Expect.equals(null, element);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(2, map(visitor).length);
List<Element> elements = map(visitor).values.toList();
Expect.notEquals(elements[0], elements[1]);
});
}
Future testLocalsFive() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
If tree =
parseStatement("if (true) { var a = 1; a; } else { var a = 2; a;}");
ResolutionResult element = visitor.visit(tree);
Expect.equals(null, element);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(6, map(visitor).length);
Block thenPart = tree.thenPart;
List statements1 = thenPart.statements.nodes.toList();
Node def1 = statements1[0].definitions.nodes.head;
Node id1 = statements1[1].expression;
Expect.equals(visitor.registry.mapping[def1],
visitor.registry.mapping[id1]);
Block elsePart = tree.elsePart;
List statements2 = elsePart.statements.nodes.toList();
Node def2 = statements2[0].definitions.nodes.head;
Node id2 = statements2[1].expression;
Expect.equals(visitor.registry.mapping[def2],
visitor.registry.mapping[id2]);
Expect.notEquals(visitor.registry.mapping[def1],
visitor.registry.mapping[def2]);
Expect.notEquals(visitor.registry.mapping[id1],
visitor.registry.mapping[id2]);
});
}
Future testParametersOne() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
FunctionExpression tree =
parseFunction("void foo(int a) { return a; }", compiler);
visitor.visit(tree);
// Check that an element has been created for the parameter.
VariableDefinitions vardef = tree.parameters.nodes.head;
Node param = vardef.definitions.nodes.head;
Expect.equals(ElementKind.PARAMETER, visitor.registry.mapping[param].kind);
// Check that 'a' in 'return a' is resolved to the parameter.
Block body = tree.body;
Return ret = body.statements.nodes.head;
Send use = ret.expression;
Expect.equals(ElementKind.PARAMETER, visitor.registry.mapping[use].kind);
Expect.equals(visitor.registry.mapping[param],
visitor.registry.mapping[use]);
});
}
Future testFor() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
For tree = parseStatement("for (int i = 0; i < 10; i = i + 1) { i = 5; }");
visitor.visit(tree);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(9, map(visitor).length);
VariableDefinitions initializer = tree.initializer;
Node iNode = initializer.definitions.nodes.head;
Element iElement = visitor.registry.mapping[iNode];
// Check that we have the expected nodes. This test relies on the mapping
// field to be a linked hash map (preserving insertion order).
Expect.isTrue(map(visitor) is LinkedHashMap);
List<Node> nodes = map(visitor).keys.toList();
List<Element> elements = map(visitor).values.toList();
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^
checkSendSet(iElement, nodes[0], elements[0]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkIdentifier(iElement, nodes[1], elements[1]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkSend(iElement, nodes[2], elements[2]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkIdentifier(iElement, nodes[3], elements[3]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkIdentifier(iElement, nodes[4], elements[4]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkSend(iElement, nodes[5], elements[5]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^^^^^
checkSendSet(iElement, nodes[6], elements[6]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkIdentifier(iElement, nodes[7], elements[7]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^
checkSendSet(iElement, nodes[8], elements[8]);
});
}
checkIdentifier(Element expected, Node node, Element actual) {
Expect.isTrue(node is Identifier, node.toDebugString());
Expect.equals(expected, actual);
}
checkSend(Element expected, Node node, Element actual) {
Expect.isTrue(node is Send, node.toDebugString());
Expect.isTrue(node is !SendSet, node.toDebugString());
Expect.equals(expected, actual);
}
checkSendSet(Element expected, Node node, Element actual) {
Expect.isTrue(node is SendSet, node.toDebugString());
Expect.equals(expected, actual);
}
Future testTypeAnnotation() {
return MockCompiler.create((MockCompiler compiler) {
String statement = "Foo bar;";
// Test that we get a warning when Foo is not defined.
Map mapping = compiler.resolveStatement(statement).map;
Expect.equals(1, mapping.length); // Only [bar] has an element.
Expect.equals(1, compiler.warnings.length);
Node warningNode = compiler.warnings[0].node;
Expect.equals(
new Message(
MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': 'Foo'}, false),
compiler.warnings[0].message);
VariableDefinitions definition = compiler.parsedTree;
Expect.equals(warningNode, definition.type);
compiler.clearMessages();
// Test that there is no warning after defining Foo.
compiler.parseScript("class Foo {}");
mapping = compiler.resolveStatement(statement).map;
Expect.equals(1, mapping.length);
Expect.equals(0, compiler.warnings.length);
// Test that 'var' does not create a warning.
mapping = compiler.resolveStatement("var foo;").map;
Expect.equals(1, mapping.length);
Expect.equals(0, compiler.warnings.length);
});
}
Future testSuperclass() {
return Future.wait([
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo extends Bar {}");
compiler.resolveStatement("Foo bar;");
Expect.equals(1, compiler.errors.length);
var cannotResolveBar = new Message(MessageKind.CANNOT_EXTEND_MALFORMED,
{'className': 'Foo', 'malformedType': 'Bar'}, false);
Expect.equals(cannotResolveBar, compiler.errors[0].message);
compiler.clearMessages();
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo extends Bar {}");
compiler.parseScript("class Bar {}");
Map mapping = compiler.resolveStatement("Foo bar;").map;
Expect.equals(1, mapping.length);
ClassElement fooElement = compiler.mainApp.find('Foo');
ClassElement barElement = compiler.mainApp.find('Bar');
Expect.equals(barElement.computeType(compiler),
fooElement.supertype);
Expect.isTrue(fooElement.interfaces.isEmpty);
Expect.isTrue(barElement.interfaces.isEmpty);
}),
]);
}
Future testVarSuperclass() {
return MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo extends var {}");
compiler.resolveStatement("Foo bar;");
Expect.equals(1, compiler.errors.length);
Expect.equals(
new Message(
MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': 'var'}, false),
compiler.errors[0].message);
compiler.clearMessages();
});
}
Future testOneInterface() {
return MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class Foo implements Bar {}");
compiler.resolveStatement("Foo bar;");
Expect.equals(1, compiler.errors.length);
Expect.equals(
new Message(
MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': 'bar'}, false),
compiler.errors[0].message);
compiler.clearMessages();
// Add the abstract class to the world and make sure everything is setup
// correctly.
compiler.parseScript("abstract class Bar {}");
ResolverVisitor visitor =
new ResolverVisitor(compiler, null,
new ResolutionRegistry.internal(compiler,
new CollectingTreeElements(null)));
compiler.resolveStatement("Foo bar;");
ClassElement fooElement = compiler.mainApp.find('Foo');
ClassElement barElement = compiler.mainApp.find('Bar');
Expect.equals(null, barElement.supertype);
Expect.isTrue(barElement.interfaces.isEmpty);
Expect.equals(barElement.computeType(compiler),
fooElement.interfaces.head);
Expect.equals(1, length(fooElement.interfaces));
});
}
Future testTwoInterfaces() {
return MockCompiler.create((MockCompiler compiler) {
compiler.parseScript(
"""abstract class I1 {}
abstract class I2 {}
class C implements I1, I2 {}""");
compiler.resolveStatement("Foo bar;");
ClassElement c = compiler.mainApp.find('C');
Element i1 = compiler.mainApp.find('I1');
Element i2 = compiler.mainApp.find('I2');
Expect.equals(2, length(c.interfaces));
Expect.equals(i1.computeType(compiler), at(c.interfaces, 0));
Expect.equals(i2.computeType(compiler), at(c.interfaces, 1));
});
}
Future testFunctionExpression() {
return MockCompiler.create((MockCompiler compiler) {
ResolverVisitor visitor = compiler.resolverVisitor();
Map mapping = compiler.resolveStatement("int f() {}").map;
Expect.equals(2, mapping.length);
Element element;
Node node;
mapping.forEach((Node n, Element e) {
if (n is FunctionExpression) {
element = e;
node = n;
}
});
Expect.equals(ElementKind.FUNCTION, element.kind);
Expect.equals('f', element.name);
Expect.equals((element as FunctionElement).node, node);
});
}
Future testNewExpression() {
return MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("class A {} foo() { print(new A()); }");
ClassElement aElement = compiler.mainApp.find('A');
FunctionElement fooElement = compiler.mainApp.find('foo');
compiler.resolver.resolve(fooElement);
Expect.isNotNull(aElement);
Expect.isNotNull(fooElement);
fooElement.node;
compiler.resolver.resolve(fooElement);
TreeElements elements = compiler.resolveStatement("new A();");
NewExpression expression =
compiler.parsedTree.asExpressionStatement().expression;
Element element = elements[expression.send];
Expect.equals(ElementKind.GENERATIVE_CONSTRUCTOR, element.kind);
Expect.isTrue(element.isSynthesized);
});
}
Future testTopLevelFields() {
return MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("int a;");
VariableElementX element = compiler.mainApp.find("a");
Expect.equals(ElementKind.FIELD, element.kind);
VariableDefinitions node = element.variables.parseNode(element, compiler);
Identifier typeName = node.type.typeName;
Expect.equals(typeName.source, 'int');
compiler.parseScript("var b, c;");
VariableElementX bElement = compiler.mainApp.find("b");
VariableElementX cElement = compiler.mainApp.find("c");
Expect.equals(ElementKind.FIELD, bElement.kind);
Expect.equals(ElementKind.FIELD, cElement.kind);
Expect.isTrue(bElement != cElement);
VariableDefinitions bNode = bElement.variables.parseNode(bElement, compiler);
VariableDefinitions cNode = cElement.variables.parseNode(cElement, compiler);
Expect.equals(bNode, cNode);
Expect.isNull(bNode.type);
Expect.isTrue(bNode.modifiers.isVar);
});
}
Future resolveConstructor(
String script, String statement, String className,
String constructor, int expectedElementCount,
{List expectedWarnings: const [],
List expectedErrors: const [],
List expectedInfos: const [],
Map<String, String> corelib}) {
MockCompiler compiler = new MockCompiler.internal(coreSource: corelib);
return compiler.init().then((_) {
compiler.parseScript(script);
compiler.resolveStatement(statement);
ClassElement classElement = compiler.mainApp.find(className);
Element element;
if (constructor != '') {
element = classElement.lookupConstructor(
new Selector.callConstructor(constructor, classElement.library));
} else {
element = classElement.lookupConstructor(
new Selector.callDefaultConstructor(classElement.library));
}
FunctionExpression tree = (element as FunctionElement).node;
ResolverVisitor visitor =
new ResolverVisitor(compiler, element,
new ResolutionRegistry.internal(compiler,
new CollectingTreeElements(element)));
new InitializerResolver(visitor).resolveInitializers(element, tree);
visitor.visit(tree.body);
Expect.equals(expectedElementCount, map(visitor).length);
compareWarningKinds(script, expectedWarnings, compiler.warnings);
compareWarningKinds(script, expectedErrors, compiler.errors);
compareWarningKinds(script, expectedInfos, compiler.infos);
});
}
Future testClassHierarchy() {
final MAIN = "main";
return Future.wait([
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""class A extends A {}
main() { return new A(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.CYCLIC_CLASS_HIERARCHY,
compiler.errors[0].message.kind);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""class A extends B {}
class B extends A {}
main() { return new A(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(2, compiler.errors.length);
Expect.equals(MessageKind.CYCLIC_CLASS_HIERARCHY,
compiler.errors[0].message.kind);
Expect.equals(MessageKind.CANNOT_FIND_CONSTRUCTOR,
compiler.errors[1].message.kind);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""abstract class A extends B {}
abstract class B extends A {}
class C implements A {}
main() { return new C(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.CYCLIC_CLASS_HIERARCHY,
compiler.errors[0].message.kind);
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""class A extends B {}
class B extends C {}
class C {}
main() { return new A(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
ClassElement aElement = compiler.mainApp.find("A");
Link<DartType> supertypes = aElement.allSupertypes;
Expect.equals(<String>['B', 'C', 'Object'].toString(),
asSortedStrings(supertypes).toString());
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""class A<T> {}
class B<Z,W> extends A<int>
implements I<Z,List<W>> {}
class I<X,Y> {}
class C extends B<bool,String> {}
main() { return new C(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
ClassElement aElement = compiler.mainApp.find("C");
Link<DartType> supertypes = aElement.allSupertypes;
// Object is once per inheritance path, that is from both A and I.
Expect.equals(<String>['A<int>', 'B<bool, String>',
'I<bool, List<String>>', 'Object'].toString(),
asSortedStrings(supertypes).toString());
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""class A<T> {}
class D extends A<E> {}
class E extends D {}
main() { return new E(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
ClassElement aElement = compiler.mainApp.find("E");
Link<DartType> supertypes = aElement.allSupertypes;
Expect.equals(<String>['A<E>', 'D', 'Object'].toString(),
asSortedStrings(supertypes).toString());
}),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""class A<T> {}
class D extends A<int> implements A<double> {}
main() { return new D(); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(1, compiler.errors.length);
Expect.equals(MessageKind.MULTI_INHERITANCE,
compiler.errors[0].message.kind);
Expect.equals(0, compiler.crashes.length);
}),
]);
}
Future testEnumDeclaration() {
final MAIN = "main";
return Future.wait([
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""enum Enum {}
main() { Enum e; }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length,
'Unexpected warnings: ${compiler.warnings}');
Expect.equals(1, compiler.errors.length,
'Unexpected errors: ${compiler.errors}');
}, enableEnums: true),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""enum Enum { A }
main() { Enum e = Enum.A; }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length,
'Unexpected warnings: ${compiler.warnings}');
Expect.equals(0, compiler.errors.length,
'Unexpected errors: ${compiler.errors}');
}, enableEnums: true),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""enum Enum { A }
main() { Enum e = Enum.B; }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(1, compiler.warnings.length,
'Unexpected warnings: ${compiler.warnings}');
Expect.equals(MessageKind.MEMBER_NOT_FOUND,
compiler.warnings[0].message.kind);
Expect.equals(0, compiler.errors.length,
'Unexpected errors: ${compiler.errors}');
}, enableEnums: true),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""enum Enum { A }
main() { List values = Enum.values; }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length,
'Unexpected warnings: ${compiler.warnings}');
Expect.equals(0, compiler.errors.length,
'Unexpected errors: ${compiler.errors}');
}, enableEnums: true),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""enum Enum { A }
main() { new Enum(0, ''); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length,
'Unexpected warnings: ${compiler.warnings}');
Expect.equals(1, compiler.errors.length,
'Unexpected errors: ${compiler.errors}');
Expect.equals(MessageKind.CANNOT_INSTANTIATE_ENUM,
compiler.errors[0].message.kind);
}, enableEnums: true),
MockCompiler.create((MockCompiler compiler) {
compiler.parseScript("""enum Enum { A }
main() { const Enum(0, ''); }""");
FunctionElement mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length,
'Unexpected warnings: ${compiler.warnings}');
Expect.equals(1, compiler.errors.length,
'Unexpected errors: ${compiler.errors}');
Expect.equals(MessageKind.CANNOT_INSTANTIATE_ENUM,
compiler.errors[0].message.kind);
}, enableEnums: true),
]);
}
Future testInitializers() {
return Future.forEach([
() {
String script =
"""class A {
int foo; int bar;
A() : this.foo = 1, bar = 2;
}""";
return resolveConstructor(script, "A a = new A();", "A", "", 2);
},
() {
String script =
"""class A {
int foo; A a;
A() : a.foo = 1;
}""";
return resolveConstructor(script, "A a = new A();", "A", "", 0,
expectedWarnings: [],
expectedErrors: [MessageKind.INVALID_RECEIVER_IN_INITIALIZER]);
},
() {
String script =
"""class A {
int foo;
A() : this.foo = 1, this.foo = 2;
}""";
return resolveConstructor(script, "A a = new A();", "A", "", 2,
expectedInfos: [MessageKind.ALREADY_INITIALIZED],
expectedErrors: [MessageKind.DUPLICATE_INITIALIZER]);
},
() {
String script =
"""class A {
A() : this.foo = 1;
}""";
return resolveConstructor(script, "A a = new A();", "A", "", 0,
expectedWarnings: [],
expectedErrors: [MessageKind.CANNOT_RESOLVE]);
},
() {
String script =
"""class A {
int foo;
int bar;
A() : this.foo = bar;
}""";
return resolveConstructor(script, "A a = new A();", "A", "", 3,
expectedWarnings: [],
expectedErrors: [MessageKind.NO_INSTANCE_AVAILABLE]);
},
() {
String script =
"""class A {
int foo() => 42;
A() : foo();
}""";
return resolveConstructor(script, "A a = new A();", "A", "", 0,
expectedWarnings: [],
expectedErrors: [MessageKind.CONSTRUCTOR_CALL_EXPECTED]);
},
() {
String script =
"""class A {
int i;
A.a() : this.b(0);
A.b(int i);
}""";
return resolveConstructor(script, "A a = new A.a();", "A", "a", 1);
},
() {
String script =
"""class A {
int i;
A.a() : i = 42, this(0);
A(int i);
}""";
return resolveConstructor(script, "A a = new A.a();", "A", "a", 2,
expectedWarnings: [],
expectedErrors:
[MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER]);
},
() {
String script =
"""class A {
int i;
A(i);
}
class B extends A {
B() : super(0);
}""";
return resolveConstructor(script, "B a = new B();", "B", "", 1);
},
() {
String script =
"""class A {
int i;
A(i);
}
class B extends A {
B() : super(0), super(1);
}""";
return resolveConstructor(script, "B b = new B();", "B", "", 2,
expectedWarnings: [],
expectedErrors: [MessageKind.DUPLICATE_SUPER_INITIALIZER]);
},
() {
String script = "";
final INVALID_OBJECT =
const { 'Object': 'class Object { Object() : super(); }' };
return resolveConstructor(script,
"Object o = new Object();", "Object", "", 1,
expectedWarnings: [],
expectedErrors: [MessageKind.SUPER_INITIALIZER_IN_OBJECT],
corelib: INVALID_OBJECT);
},
], (f) => f());
}
map(ResolverVisitor visitor) {
CollectingTreeElements elements = visitor.registry.mapping;
return elements.map;
}
at(Link link, int index) => (index == 0) ? link.head : at(link.tail, index - 1);
List<String> asSortedStrings(Link link) {
List<String> result = <String>[];
for (; !link.isEmpty; link = link.tail) result.add(link.head.toString());
result.sort((s1, s2) => s1.compareTo(s2));
return result;
}
Future compileScript(String source) {
Uri uri = new Uri(scheme: 'source');
MockCompiler compiler = compilerFor(source, uri);
compiler.diagnosticHandler = createHandler(compiler, source);
return compiler.runCompiler(uri).then((_) {
return compiler;
});
}
checkMemberResolved(compiler, className, memberName) {
ClassElement cls = findElement(compiler, className);
Element memberElement = cls.lookupLocalMember(memberName);
Expect.isNotNull(memberElement);
Expect.isTrue(
compiler.enqueuer.resolution.hasBeenResolved(memberElement));
}
testToString() {
final script = r"class C { toString() => 'C'; } main() { '${new C()}'; }";
asyncTest(() => compileScript(script).then((compiler) {
checkMemberResolved(compiler, 'C', 'toString');
}));
}
operatorName(op, isUnary) {
return Elements.constructOperatorName(op, isUnary);
}
testIndexedOperator() {
final script = r"""
class C {
operator[](ix) => ix;
operator[]=(ix, v) {}
}
main() { var c = new C(); c[0]++; }""";
asyncTest(() => compileScript(script).then((compiler) {
checkMemberResolved(compiler, 'C', operatorName('[]', false));
checkMemberResolved(compiler, 'C', operatorName('[]=', false));
}));
}
testIncrementsAndDecrements() {
final script = r"""
class A { operator+(o)=>null; }
class B { operator+(o)=>null; }
class C { operator-(o)=>null; }
class D { operator-(o)=>null; }
main() {
var a = new A();
a++;
var b = new B();
++b;
var c = new C();
c--;
var d = new D();
--d;
}""";
asyncTest(() => compileScript(script).then((compiler) {
checkMemberResolved(compiler, 'A', operatorName('+', false));
checkMemberResolved(compiler, 'B', operatorName('+', false));
checkMemberResolved(compiler, 'C', operatorName('-', false));
checkMemberResolved(compiler, 'D', operatorName('-', false));
}));
}
testOverrideHashCodeCheck() {
final script = r"""
class A {
operator==(other) => true;
}
class B {
operator==(other) => true;
get hashCode => 0;
}
main() {
new A() == new B();
}""";
asyncTest(() => compileScript(script).then((compiler) {
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.infos.length);
Expect.equals(1, compiler.hints.length);
Expect.equals(MessageKind.OVERRIDE_EQUALS_NOT_HASH_CODE,
compiler.hints[0].message.kind);
Expect.equals(0, compiler.errors.length);
}));
}
testConstConstructorAndNonFinalFields() {
void expect(compiler, List errors, List infos) {
Expect.equals(errors.length, compiler.errors.length);
for (int i = 0 ; i < errors.length ; i++) {
Expect.equals(errors[i], compiler.errors[i].message.kind);
}
Expect.equals(0, compiler.warnings.length);
Expect.equals(infos.length, compiler.infos.length);
for (int i = 0 ; i < infos.length ; i++) {
Expect.equals(infos[i], compiler.infos[i].message.kind);
}
}
final script1 = r"""
class A {
var a;
const A(this.a);
}
main() {
new A(0);
}""";
asyncTest(() => compileScript(script1).then((compiler) {
expect(compiler,
[MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS],
[MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_FIELD]);
}));
final script2 = r"""
class A {
var a;
var b;
const A(this.a, this.b);
const A.named(this.a, this.b);
}
main() {
new A(0, 1);
}""";
asyncTest(() => compileScript(script2).then((compiler) {
expect(compiler,
[MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS],
[MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_CONSTRUCTOR,
MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_CONSTRUCTOR,
MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_FIELD,
MessageKind.CONST_CONSTRUCTOR_WITH_NONFINAL_FIELDS_FIELD]);
}));
}