blob: 236b0bd22099886f24134714e96ee4a08e9a5a60 [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 'dart:async';
import 'dart:collection';
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:compiler/src/constants/expressions.dart';
import 'package:compiler/src/dart_types.dart';
import 'package:compiler/src/elements/modelx.dart';
import 'package:compiler/src/resolution/constructors.dart';
import 'package:compiler/src/resolution/members.dart';
import 'package:compiler/src/resolution/registry.dart';
import 'package:compiler/src/resolution/resolution_result.dart';
import 'package:compiler/src/resolution/scope.dart';
import 'package:compiler/src/resolution/tree_elements.dart';
import 'compiler_helper.dart';
import 'link_helper.dart';
import 'parser_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));
// A VariableDefinitions does not have an element.
Expect.equals(const NoneResult(), result);
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.element;
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,
testCantAssignMethods,
testCantAssignFinalAndConsts,
testAwaitHint,
testConstantExpressions,
], (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.resolution);
foo.lookupLocalMember('t').computeType(compiler.resolution);
foo.lookupLocalMember('foo').computeType(compiler.resolution);
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(compiler,
new CollectingTreeElements(fooB)));
FunctionExpression node =
(fooB as FunctionElementX).parseNode(compiler.parsing);
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(compiler,
new CollectingTreeElements(funElement)));
FunctionExpression function =
(funElement as FunctionElementX).parseNode(compiler.parsing);
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(compiler,
new CollectingTreeElements(funElement)));
FunctionExpression function =
(funElement as FunctionElementX).parseNode(compiler.parsing);
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(
MessageTemplate.TEMPLATES[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 result = visitor.visit(tree);
Expect.equals(const NoneResult(), result);
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 result = visitor.visit(tree);
Expect.equals(const NoneResult(), result);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(2, 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 result = visitor.visit(tree);
Expect.equals(const NoneResult(), result);
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 result = visitor.visit(tree);
Expect.equals(const NoneResult(), result);
MethodScope scope = visitor.scope;
Expect.equals(0, scope.elements.length);
Expect.equals(4, 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(5, 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; };
// ^
checkSend(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; };
// ^^^^^^^^^
checkSendSet(iElement, nodes[3], elements[3]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^
checkSendSet(iElement, nodes[4], elements[4]);
});
}
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(
MessageTemplate.TEMPLATES[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(
MessageTemplate.TEMPLATES[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.resolution),
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(
MessageTemplate.TEMPLATES[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(
MessageTemplate.TEMPLATES[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(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.resolution),
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');
ClassElement i1 = compiler.mainApp.find('I1');
ClassElement i2 = compiler.mainApp.find('I2');
Expect.equals(2, length(c.interfaces));
Expect.equals(i1.computeType(compiler.resolution), at(c.interfaces, 0));
Expect.equals(i2.computeType(compiler.resolution), 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.parsing);
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.parsing);
VariableDefinitions cNode =
cElement.variables.parseNode(cElement, compiler.parsing);
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;
element = classElement.lookupConstructor(constructor);
FunctionExpression tree = (element as FunctionElement).node;
ResolverVisitor visitor =
new ResolverVisitor(compiler, element,
new ResolutionRegistry(compiler,
new CollectingTreeElements(element)));
new InitializerResolver(visitor, element, tree).resolveInitializers();
visitor.visit(tree.body);
Expect.equals(expectedElementCount, map(visitor).length,
"${map(visitor).values} for '$statement' in context of `$script`");
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}');
}),
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}');
}),
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}');
}),
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}');
}),
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);
}),
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);
}),
]);
}
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", "", 1,
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", "", 2,
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());
}
Future testConstantExpressions() {
const Map<String, List<String>> testedConstants = const {
'null': const ['null'],
'true': const ['true'],
'0': const ['0'],
'0.0': const ['0.0'],
'"foo"': const ['"foo"'],
'#a': const ['#a'],
'0 + 1': const ['0', '1', '0 + 1'],
'0 * 1': const ['0', '1', '0 * 1'],
'0 * 1 + 2': const ['0', '1', '0 * 1', '2', '0 * 1 + 2'],
'0 + 1 * 2': const ['0', '1', '2', '1 * 2', '0 + 1 * 2'],
'-(1)': const ['1', '-1'],
'-(1 * 4)': const ['1', '4', '1 * 4', '-(1 * 4)'],
'true ? 0 : 1': const ['true', '0', '1', 'true ? 0 : 1'],
'"a" "b"': const ['"a"', '"b"', '"ab"'],
'"a" "b" "c"': const ['"a"', '"b"', '"c"', '"bc"', r'"a${"bc"}"'],
r'"a${0}b"': const ['"a"', '0', '"b"', r'"a${0}b"'],
r'"a${0}b${1}"': const ['"a"', '0', '"b"', '1', '""', r'"a${0}b${1}"'],
'true || false': const ['true', 'false', 'true || false'],
'true && false': const ['true', 'false', 'true && false'],
'!true': const ['true', '!true'],
'const []': const ['const []'],
'const <int>[]': const ['const <int>[]'],
'const [0, 1, 2]': const ['0', '1', '2', 'const [0, 1, 2]'],
'const <int>[0, 1, 2]': const ['0', '1', '2', 'const <int>[0, 1, 2]'],
'const {}': const ['const {}'],
'const <String, int>{}': const ['const <String, int>{}'],
'const {"a": 0, "b": 1, "c": 2}':
const ['"a"', '0', '"b"', '1', '"c"', '2',
'const {"a": 0, "b": 1, "c": 2}'],
'const <String, int>{"a": 0, "b": 1, "c": 2}':
const ['"a"', '0', '"b"', '1', '"c"', '2',
'const <String, int>{"a": 0, "b": 1, "c": 2}'],
};
return Future.forEach(testedConstants.keys, (String constant) {
return MockCompiler.create((MockCompiler compiler) {
CollectingTreeElements elements =
compiler.resolveStatement("main() => $constant;");
List<String> expectedConstants = testedConstants[constant];
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
List<ConstantExpression> constants = elements.constants;
String constantsText =
'[${constants.map((c) => c.getText()).join(', ')}]';
Expect.equals(expectedConstants.length, constants.length,
"Expected ${expectedConstants.length} constants for `${constant}` "
"found $constantsText.");
for (int index = 0; index < expectedConstants.length; index++) {
Expect.equals(expectedConstants[index], constants[index].getText(),
"Expected ${expectedConstants} for `$constant`, "
"found $constantsText.");
}
});
});
}
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.hasBeenProcessed(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]);
}));
}
testCantAssignMethods() {
// Can't override local functions
checkWarningOn('''
main() {
mname() { mname = 2; };
mname();
}
''', [MessageKind.ASSIGNING_METHOD]);
checkWarningOn('''
main() {
mname() { };
mname = 3;
}
''', [MessageKind.ASSIGNING_METHOD]);
// Can't override top-level functions
checkWarningOn('''
m() {}
main() { m = 4; }
''', [MessageKind.ASSIGNING_METHOD,
// TODO(johnniwinther): Avoid duplicate warnings.
MessageKind.NOT_ASSIGNABLE]);
// Can't override instance methods
checkWarningOn('''
main() { new B().bar(); }
class B {
mname() {}
bar() {
mname = () => null;
}
}
''', [MessageKind.SETTER_NOT_FOUND]);
checkWarningOn('''
main() { new B().bar(); }
class B {
mname() {}
bar() {
this.mname = () => null;
}
}
''', [MessageKind.SETTER_NOT_FOUND]);
// Can't override super methods
checkWarningOn('''
main() { new B().bar(); }
class A {
mname() {}
}
class B extends A {
bar() {
super.mname = () => 6;
}
}
''', [MessageKind.ASSIGNING_METHOD_IN_SUPER,
// TODO(johnniwinther): Avoid duplicate warnings.
MessageKind.SETTER_NOT_FOUND]);
// But index operators should be OK
checkWarningOn('''
main() { new B().bar(); }
class B {
operator[]=(x, y) {}
bar() {
this[1] = 3; // This is OK
}
}
''', []);
checkWarningOn('''
main() { new B().bar(); }
class A {
operator[]=(x, y) {}
}
class B extends A {
bar() {
super[1] = 3; // This is OK
}
}
''', []);
}
testCantAssignFinalAndConsts() {
// Can't write final or const locals.
checkWarningOn('''
main() {
final x = 1;
x = 2;
}
''', [MessageKind.CANNOT_RESOLVE_SETTER]);
checkWarningOn('''
main() {
const x = 1;
x = 2;
}
''', [MessageKind.CANNOT_RESOLVE_SETTER]);
checkWarningOn('''
final x = 1;
main() { x = 3; }
''', [MessageKind.CANNOT_RESOLVE_SETTER]);
checkWarningOn('''
const x = 1;
main() { x = 3; }
''', [MessageKind.CANNOT_RESOLVE_SETTER]);
// Detect assignments to final fields:
checkWarningOn('''
main() => new B().m();
class B {
final x = 1;
m() { x = 2; }
}
''', [MessageKind.SETTER_NOT_FOUND]);
// ... even if 'this' is explicit:
checkWarningOn('''
main() => new B().m();
class B {
final x = 1;
m() { this.x = 2; }
}
''', [MessageKind.SETTER_NOT_FOUND]);
// ... and in super class:
checkWarningOn('''
main() => new B().m();
class A {
final x = 1;
}
class B extends A {
m() { super.x = 2; }
}
''', [MessageKind.ASSIGNING_FINAL_FIELD_IN_SUPER,
// TODO(johnniwinther): Avoid duplicate warnings.
MessageKind.SETTER_NOT_FOUND]);
// But non-final fields are OK:
checkWarningOn('''
main() => new B().m();
class A {
int x = 1;
}
class B extends A {
m() { super.x = 2; }
}
''', []);
// Check getter without setter.
checkWarningOn('''
main() => new B().m();
class A {
get x => 1;
}
class B extends A {
m() { super.x = 2; }
}
''', [MessageKind.SETTER_NOT_FOUND_IN_SUPER,
// TODO(johnniwinther): Avoid duplicate warnings.
MessageKind.SETTER_NOT_FOUND]);
}
/// Helper to test that [script] produces all the given [warnings].
checkWarningOn(String script, List<MessageKind> warnings) {
Expect.isTrue(warnings.length >= 0 && warnings.length <= 2);
asyncTest(() => compileScript(script).then((compiler) {
Expect.equals(0, compiler.errors.length,
'Unexpected errors in\n$script\n${compiler.errors}');
Expect.equals(warnings.length, compiler.warnings.length,
'Unexpected warnings in\n$script\n'
'Expected:$warnings\nFound:${compiler.warnings}');
for (int i = 0; i < warnings.length; i++) {
Expect.equals(warnings[i], compiler.warnings[i].message.kind);
}
}));
}
testAwaitHint() {
check(String script, {String className, String functionName}) {
var prefix = className == null
? "Cannot resolve 'await'"
: "No member named 'await' in class '$className'";
var where = functionName == null
? 'the enclosing function' : "'$functionName'";
asyncTest(() => compileScript(script).then((compiler) {
Expect.equals(0, compiler.errors.length);
Expect.equals(1, compiler.warnings.length);
Expect.equals("$prefix.\n"
"Did you mean to add the 'async' marker to $where?",
'${compiler.warnings[0].message}');
}));
}
check('main() { await -3; }', functionName: 'main');
check('main() { () => await -3; }');
check('foo() => await -3; main() => foo();', functionName: 'foo');
check('''
class A {
m() => await - 3;
}
main() => new A().m();
''', className: 'A', functionName: 'm');
check('''
class A {
static m() => await - 3;
}
main() => A.m();
''', functionName: 'm');
check('''
class A {
m() => () => await - 3;
}
main() => new A().m();
''', className: 'A');
}