blob: a6a382cb0fe10d69dbfbb68a93f32c4b0017c379 [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 "../../../sdk/lib/_internal/compiler/implementation/resolution/resolution.dart";
import "compiler_helper.dart";
import "parser_helper.dart";
import '../../../sdk/lib/_internal/compiler/implementation/dart_types.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 = [];
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, new Link.fromList(locals), null, null);
return new VariableDefinitions(null, Modifiers.EMPTY, definitions);
}
testLocals(List variables) {
MockCompiler compiler = new MockCompiler();
ResolverVisitor visitor = compiler.resolverVisitor();
Element element = visitor.visit(createLocals(variables));
// 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);
final VariableElement variableElement = visitor.visit(id);
MethodScope scope = visitor.scope;
Expect.equals(variableElement, scope.elements[name]);
}
return compiler;
}
main() {
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();
testInitializers();
testThis();
testSuperCalls();
testTypeVariables();
testToString();
testIndexedOperator();
testIncrementsAndDecrements();
testOverrideHashCodeCheck();
testSupertypeOrder();
testConstructorArgumentMismatch();
testConstConstructorAndNonFinalFields();
}
testSupertypeOrder() {
MockCompiler compiler = new MockCompiler();
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());
compiler = new MockCompiler();
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('[ Foo, X<Bar>, X<Foo>, Object ]',
classBar.allSupertypes.toString());
}
testTypeVariables() {
matchResolvedTypes(visitor, text, name, expectedElements) {
VariableDefinitions definition = parseStatement(text);
visitor.visit(definition.type);
InterfaceType type = visitor.mapping.getType(definition.type);
Expect.equals(definition.type.typeArguments.slowLength(),
length(type.typeArguments));
int index = 0;
Link<DartType> arguments = type.typeArguments;
while (!arguments.isEmpty) {
Expect.equals(true, index < expectedElements.length);
Expect.equals(expectedElements[index], arguments.head.element);
index++;
arguments = arguments.tail;
}
Expect.equals(index, expectedElements.length);
}
MockCompiler compiler = new MockCompiler();
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]);
compiler = new MockCompiler();
compiler.parseScript('class Foo<T, U> {}');
compiler.resolveStatement('Foo<notype, int> x;');
Expect.equals(1, compiler.warnings.length);
Expect.equals(MessageKind.CANNOT_RESOLVE_TYPE.warning,
compiler.warnings[0].message.kind);
Expect.equals(0, compiler.errors.length);
compiler = new MockCompiler();
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.warning,
compiler.warnings[0].message.kind);
compiler = new MockCompiler();
compiler.parseScript('class Foo<T> {'
' Foo<T> t;'
' foo(Foo<T> f) {}'
' bar() { g(Foo<T> f) {}; g(); }'
'}');
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);
}
testSuperCalls() {
MockCompiler compiler = new MockCompiler();
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 CollectingTreeElements(fooB));
FunctionExpression node = fooB.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);
}
testThis() {
MockCompiler compiler = new MockCompiler();
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 CollectingTreeElements(funElement));
FunctionExpression function = funElement.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);
compiler = new MockCompiler();
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);
compiler = new MockCompiler();
compiler.parseScript("class Foo { static foo() { return this; } }");
compiler.resolveStatement("Foo foo;");
fooElement = compiler.mainApp.find("Foo");
funElement =
fooElement.lookupLocalMember("foo");
visitor = new ResolverVisitor(compiler, funElement,
new CollectingTreeElements(funElement));
function = funElement.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);
}
testLocalsOne() {
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]]);
MockCompiler compiler = testLocals([["foo", false], ["foo", false]]);
Expect.equals(1, compiler.errors.length);
Expect.equals(
new Message(MessageKind.DUPLICATE_DEFINITION, {'name': 'foo'}, false),
compiler.errors[0].message);
}
testLocalsTwo() {
MockCompiler compiler = new MockCompiler();
ResolverVisitor visitor = compiler.resolverVisitor();
Node tree = parseStatement("if (true) { var a = 1; var b = 2; }");
Element 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]);
}
testLocalsThree() {
MockCompiler compiler = new MockCompiler();
ResolverVisitor visitor = compiler.resolverVisitor();
Node tree = parseStatement("{ var a = 1; if (true) { a; } }");
Element 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]);
}
testLocalsFour() {
MockCompiler compiler = new MockCompiler();
ResolverVisitor visitor = compiler.resolverVisitor();
Node tree = parseStatement("{ var a = 1; if (true) { var a = 1; } }");
Element 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]);
}
testLocalsFive() {
MockCompiler compiler = new MockCompiler();
ResolverVisitor visitor = compiler.resolverVisitor();
If tree = parseStatement("if (true) { var a = 1; a; } else { var a = 2; a;}");
Element 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.mapping[def1], visitor.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.mapping[def2], visitor.mapping[id2]);
Expect.notEquals(visitor.mapping[def1], visitor.mapping[def2]);
Expect.notEquals(visitor.mapping[id1], visitor.mapping[id2]);
}
testParametersOne() {
MockCompiler compiler = new MockCompiler();
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.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.mapping[use].kind);
Expect.equals(visitor.mapping[param], visitor.mapping[use]);
}
testFor() {
MockCompiler compiler = new MockCompiler();
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(10, map(visitor).length);
VariableDefinitions initializer = tree.initializer;
Node iNode = initializer.definitions.nodes.head;
Element iElement = visitor.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; };
// ^^^
Expect.isTrue(nodes[0] is TypeAnnotation);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^
checkSendSet(iElement, nodes[1], elements[1]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkIdentifier(iElement, nodes[2], elements[2]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkSend(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; };
// ^
checkIdentifier(iElement, nodes[5], elements[5]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkSend(iElement, nodes[6], elements[6]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^^^^^
checkSendSet(iElement, nodes[7], elements[7]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^
checkIdentifier(iElement, nodes[8], elements[8]);
// for (int i = 0; i < 10; i = i + 1) { i = 5; };
// ^^^^^
checkSendSet(iElement, nodes[9], elements[9]);
}
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);
}
testTypeAnnotation() {
MockCompiler compiler = new MockCompiler();
String statement = "Foo bar;";
// Test that we get a warning when Foo is not defined.
Map mapping = compiler.resolveStatement(statement).map;
Expect.equals(2, mapping.length); // Both Foo and bar have an element.
Expect.equals(1, compiler.warnings.length);
Node warningNode = compiler.warnings[0].node;
Expect.equals(
new Message(
MessageKind.CANNOT_RESOLVE_TYPE.warning, {'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(2, 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);
}
testSuperclass() {
MockCompiler compiler = new MockCompiler();
compiler.parseScript("class Foo extends Bar {}");
compiler.resolveStatement("Foo bar;");
Expect.equals(1, compiler.errors.length);
var cannotResolveBar = new Message(MessageKind.CANNOT_EXTEND_MALFORMED,
{'typeName': 'Bar'}, false);
Expect.equals(cannotResolveBar, compiler.errors[0].message);
compiler.clearMessages();
compiler = new MockCompiler();
compiler.parseScript("class Foo extends Bar {}");
compiler.parseScript("class Bar {}");
Map mapping = compiler.resolveStatement("Foo bar;").map;
Expect.equals(2, 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);
}
testVarSuperclass() {
MockCompiler compiler = new MockCompiler();
compiler.parseScript("class Foo extends var {}");
compiler.resolveStatement("Foo bar;");
Expect.equals(1, compiler.errors.length);
Expect.equals(
new Message(
MessageKind.CANNOT_RESOLVE_TYPE.warning, {'typeName': 'var'}, false),
compiler.errors[0].message);
compiler.clearMessages();
}
testOneInterface() {
MockCompiler compiler = new MockCompiler();
compiler.parseScript("class Foo implements Bar {}");
compiler.resolveStatement("Foo bar;");
Expect.equals(1, compiler.errors.length);
Expect.equals(
new Message(
MessageKind.CANNOT_RESOLVE_TYPE.warning, {'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 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));
}
testTwoInterfaces() {
MockCompiler compiler = new MockCompiler();
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));
}
testFunctionExpression() {
MockCompiler compiler = new MockCompiler();
ResolverVisitor visitor = compiler.resolverVisitor();
Map mapping = compiler.resolveStatement("int f() {}").map;
Expect.equals(3, 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.parseNode(compiler), node);
}
testNewExpression() {
MockCompiler compiler = new MockCompiler();
compiler.parseScript("class A {} foo() { print(new A()); }");
ClassElement aElement = compiler.mainApp.find('A');
FunctionElement fooElement = compiler.mainApp.find('foo');
Expect.isNotNull(aElement);
Expect.isNotNull(fooElement);
fooElement.parseNode(compiler);
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);
}
testConstructorArgumentMismatch() {
String script = "class A {} foo() { print(new A(42)); }";
MockCompiler compiler = new MockCompiler();
compiler.parseScript(script);
FunctionElement fooElement = compiler.mainApp.find('foo');
Expect.isNotNull(fooElement);
fooElement.parseNode(compiler);
compiler.resolver.resolve(fooElement);
compareWarningKinds(
script, [MessageKind.INVALID_ARGUMENTS.warning], compiler.warnings);
compareWarningKinds(script, [], compiler.errors);
}
testTopLevelFields() {
MockCompiler compiler = new MockCompiler();
compiler.parseScript("int a;");
VariableElement element = compiler.mainApp.find("a");
Expect.equals(ElementKind.FIELD, element.kind);
VariableDefinitions node = element.variables.parseNode(compiler);
Identifier typeName = node.type.typeName;
Expect.equals(typeName.source, 'int');
compiler.parseScript("var b, c;");
VariableElement bElement = compiler.mainApp.find("b");
VariableElement 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(compiler);
VariableDefinitions cNode = cElement.variables.parseNode(compiler);
Expect.equals(bNode, cNode);
Expect.isNull(bNode.type);
Expect.isTrue(bNode.modifiers.isVar());
}
resolveConstructor(String script, String statement, String className,
String constructor, int expectedElementCount,
{List expectedWarnings: const [],
List expectedErrors: const [],
List expectedInfos: const [],
String corelib: DEFAULT_CORELIB}) {
MockCompiler compiler = new MockCompiler(coreSource: corelib);
compiler.parseScript(script);
compiler.resolveStatement(statement);
ClassElement classElement =
compiler.mainApp.find(className);
Element element;
if (constructor != '') {
element = classElement.lookupConstructor(
new Selector.callConstructor(constructor,
classElement.getLibrary()));
} else {
element = classElement.lookupConstructor(
new Selector.callDefaultConstructor(classElement.getLibrary()));
}
FunctionExpression tree = element.parseNode(compiler);
ResolverVisitor visitor =
new ResolverVisitor(compiler, element,
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);
}
testClassHierarchy() {
final MAIN = "main";
MockCompiler compiler = new MockCompiler();
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.error,
compiler.errors[1].message.kind);
compiler = new MockCompiler();
compiler.parseScript("""abstract class A extends B {}
abstract class B extends A {}
class C implements A {}
main() { return new C(); }""");
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);
compiler = new MockCompiler();
compiler.parseScript("""class A extends B {}
class B extends C {}
class C {}
main() { return new A(); }""");
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());
compiler = new MockCompiler();
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(); }""");
mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
aElement = compiler.mainApp.find("C");
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());
compiler = new MockCompiler();
compiler.parseScript("""class A<T> {}
class D extends A<E> {}
class E extends D {}
main() { return new E(); }""");
mainElement = compiler.mainApp.find(MAIN);
compiler.resolver.resolve(mainElement);
Expect.equals(0, compiler.warnings.length);
Expect.equals(0, compiler.errors.length);
aElement = compiler.mainApp.find("E");
supertypes = aElement.allSupertypes;
Expect.equals(<String>['A<E>', 'D', 'Object'].toString(),
asSortedStrings(supertypes).toString());
}
testInitializers() {
String script;
script = """class A {
int foo; int bar;
A() : this.foo = 1, bar = 2;
}""";
resolveConstructor(script, "A a = new A();", "A", "", 2);
script = """class A {
int foo; A a;
A() : a.foo = 1;
}""";
resolveConstructor(script, "A a = new A();", "A", "", 0,
expectedWarnings: [],
expectedErrors:
[MessageKind.INVALID_RECEIVER_IN_INITIALIZER]);
script = """class A {
int foo;
A() : this.foo = 1, this.foo = 2;
}""";
resolveConstructor(script, "A a = new A();", "A", "", 2,
expectedInfos: [MessageKind.ALREADY_INITIALIZED],
expectedErrors: [MessageKind.DUPLICATE_INITIALIZER]);
script = """class A {
A() : this.foo = 1;
}""";
resolveConstructor(script, "A a = new A();", "A", "", 0,
expectedWarnings: [],
expectedErrors: [MessageKind.CANNOT_RESOLVE.error]);
script = """class A {
int foo;
int bar;
A() : this.foo = bar;
}""";
resolveConstructor(script, "A a = new A();", "A", "", 3,
expectedWarnings: [],
expectedErrors: [MessageKind.NO_INSTANCE_AVAILABLE]);
script = """class A {
int foo() => 42;
A() : foo();
}""";
resolveConstructor(script, "A a = new A();", "A", "", 0,
expectedWarnings: [],
expectedErrors: [MessageKind.CONSTRUCTOR_CALL_EXPECTED]);
script = """class A {
int i;
A.a() : this.b(0);
A.b(int i);
}""";
resolveConstructor(script, "A a = new A.a();", "A", "a", 1);
script = """class A {
int i;
A.a() : i = 42, this(0);
A(int i);
}""";
resolveConstructor(script, "A a = new A.a();", "A", "a", 2,
expectedWarnings: [],
expectedErrors:
[MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER]);
script = """class A {
int i;
A(i);
}
class B extends A {
B() : super(0);
}""";
resolveConstructor(script, "B a = new B();", "B", "", 1);
script = """class A {
int i;
A(i);
}
class B extends A {
B() : super(0), super(1);
}""";
resolveConstructor(script, "B b = new B();", "B", "", 2,
expectedWarnings: [],
expectedErrors: [MessageKind.DUPLICATE_SUPER_INITIALIZER]);
script = "";
final String CORELIB_WITH_INVALID_OBJECT =
'''print(var obj) {}
class int {}
class double {}
class bool {}
class String {}
class num {}
class Function {}
class List {}
class Map {}
class Closure {}
class Null {}
class StackTrace {}
class Dynamic_ {}
class Type {}
class Object { Object() : super(); }''';
resolveConstructor(script, "Object o = new Object();", "Object", "", 1,
expectedWarnings: [],
expectedErrors: [MessageKind.SUPER_INITIALIZER_IN_OBJECT],
corelib: CORELIB_WITH_INVALID_OBJECT);
}
map(ResolverVisitor visitor) {
CollectingTreeElements elements = visitor.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.isNotNull(
compiler.enqueuer.resolution.getCachedElements(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]);
}));
}