| // 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 'package:compiler/src/universe/use.dart'; |
| import 'package:compiler/src/universe/world_impact.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"); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals( |
| MessageKind.MULTI_INHERITANCE, collector.errors.first.message.kind); |
| Expect.equals(0, collector.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.coreClasses.intClass, compiler.coreClasses.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;'); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.warnings.length); |
| Expect.equals(MessageKind.CANNOT_RESOLVE_TYPE, |
| collector.warnings.first.message.kind); |
| Expect.equals(0, collector.errors.length); |
| }), |
| MockCompiler.create((MockCompiler compiler) { |
| compiler.parseScript('class Foo<T, U> {}'); |
| compiler.resolveStatement('var x = new Foo<notype, int>();'); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.warnings.length); |
| Expect.equals(0, collector.errors.length); |
| Expect.equals(MessageKind.CANNOT_RESOLVE_TYPE, |
| collector.warnings.first.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); |
| MemberElement tMember = foo.lookupLocalMember('t'); |
| tMember.computeType(compiler.resolution); |
| MemberElement fooMember = foo.lookupLocalMember('foo'); |
| fooMember.computeType(compiler.resolution); |
| compiler.resolver.resolve(foo.lookupLocalMember('bar')); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(0, collector.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.resolution, |
| fooB, |
| new ResolutionRegistry( |
| compiler.backend, new CollectingTreeElements(fooB)), |
| scope: new MockTypeVariablesScope(classB.buildScope())); |
| FunctionExpression node = |
| (fooB as FunctionElementX).parseNode(compiler.parsingContext); |
| 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.enqueuer.resolution.applyImpact(new WorldImpactBuilderImpl() |
| ..registerStaticUse(new StaticUse.foreignUse(funElement))); |
| compiler.processQueue(compiler.enqueuer.resolution, null); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals(MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL, |
| collector.errors.first.message.kind); |
| Expect.equals(2, collector.infos.length); |
| Expect.equals(MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE, |
| collector.infos.first.message.kind); |
| Expect.equals(MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE, |
| collector.infos.elementAt(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.resolution, |
| funElement, |
| new ResolutionRegistry( |
| compiler.backend, new CollectingTreeElements(funElement)), |
| scope: new MockTypeVariablesScope(fooElement.buildScope())); |
| FunctionExpression function = |
| (funElement as FunctionElementX).parseNode(compiler.parsingContext); |
| visitor.visit(function.body); |
| Map mapping = map(visitor); |
| List<Element> values = mapping.values.toList(); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, mapping.length); |
| Expect.equals(0, collector.warnings.length); |
| }), |
| MockCompiler.create((MockCompiler compiler) { |
| compiler.resolveStatement("main() { return this; }"); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals(MessageKind.NO_INSTANCE_AVAILABLE, |
| collector.errors.first.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.resolution, |
| funElement, |
| new ResolutionRegistry( |
| compiler.backend, new CollectingTreeElements(funElement)), |
| scope: new MockTypeVariablesScope(fooElement.buildScope())); |
| FunctionExpression function = |
| (funElement as FunctionElementX).parseNode(compiler.parsingContext); |
| visitor.visit(function.body); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals(MessageKind.NO_INSTANCE_AVAILABLE, |
| collector.errors.first.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) { |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.errors.length); |
| Expect.equals( |
| new Message( |
| MessageTemplate.TEMPLATES[MessageKind.DUPLICATE_DEFINITION], |
| {'name': 'foo'}, |
| false), |
| collector.errors.first.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. |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.warnings.length); |
| |
| Expect.equals( |
| new Message(MessageTemplate.TEMPLATES[MessageKind.CANNOT_RESOLVE_TYPE], |
| {'typeName': 'Foo'}, false), |
| collector.warnings.first.message); |
| collector.clear(); |
| |
| // 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, collector.warnings.length); |
| |
| // Test that 'var' does not create a warning. |
| mapping = compiler.resolveStatement("var foo;").map; |
| Expect.equals(1, mapping.length); |
| Expect.equals(0, collector.warnings.length); |
| }); |
| } |
| |
| Future testSuperclass() { |
| return Future.wait([ |
| MockCompiler.create((MockCompiler compiler) { |
| compiler.parseScript("class Foo extends Bar {}"); |
| compiler.resolveStatement("Foo bar;"); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.errors.length); |
| var cannotResolveBar = new Message( |
| MessageTemplate.TEMPLATES[MessageKind.CANNOT_EXTEND_MALFORMED], |
| {'className': 'Foo', 'malformedType': 'Bar'}, |
| false); |
| Expect.equals(cannotResolveBar, collector.errors.first.message); |
| collector.clear(); |
| }), |
| 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;"); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.errors.length); |
| Expect.equals( |
| new Message(MessageTemplate.TEMPLATES[MessageKind.CANNOT_RESOLVE_TYPE], |
| {'typeName': 'var'}, false), |
| collector.errors.first.message); |
| collector.clear(); |
| }); |
| } |
| |
| Future testOneInterface() { |
| return MockCompiler.create((MockCompiler compiler) { |
| compiler.parseScript("class Foo implements Bar {}"); |
| compiler.resolveStatement("Foo bar;"); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.errors.length); |
| Expect.equals( |
| new Message(MessageTemplate.TEMPLATES[MessageKind.CANNOT_RESOLVE_TYPE], |
| {'typeName': 'bar'}, false), |
| collector.errors.first.message); |
| collector.clear(); |
| |
| // Add the abstract class to the world and make sure everything is setup |
| // correctly. |
| compiler.parseScript("abstract class Bar {}"); |
| |
| ResolverVisitor visitor = new ResolverVisitor( |
| compiler.resolution, |
| null, |
| new ResolutionRegistry( |
| compiler.backend, 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) { |
| 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.parsingContext); |
| 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.parsingContext); |
| VariableDefinitions cNode = |
| cElement.variables.parseNode(cElement, compiler.parsingContext); |
| 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.resolution, |
| element, |
| new ResolutionRegistry( |
| compiler.backend, new CollectingTreeElements(element)), |
| scope: classElement.buildScope()); |
| 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`"); |
| |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| compareWarningKinds(script, expectedWarnings, collector.warnings); |
| compareWarningKinds(script, expectedErrors, collector.errors); |
| compareWarningKinds(script, expectedInfos, collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals(MessageKind.CYCLIC_CLASS_HIERARCHY, |
| collector.errors.first.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(2, collector.errors.length); |
| Expect.equals(MessageKind.CYCLIC_CLASS_HIERARCHY, |
| collector.errors.first.message.kind); |
| Expect.equals(MessageKind.CANNOT_FIND_UNNAMED_CONSTRUCTOR, |
| collector.errors.elementAt(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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals(MessageKind.CYCLIC_CLASS_HIERARCHY, |
| collector.errors.first.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(0, collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(0, collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(0, collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(1, collector.errors.length); |
| Expect.equals( |
| MessageKind.MULTI_INHERITANCE, collector.errors.first.message.kind); |
| Expect.equals(0, collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length, |
| 'Unexpected warnings: ${collector.warnings}'); |
| Expect.equals( |
| 1, collector.errors.length, 'Unexpected errors: ${collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length, |
| 'Unexpected warnings: ${collector.warnings}'); |
| Expect.equals( |
| 0, collector.errors.length, 'Unexpected errors: ${collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(1, collector.warnings.length, |
| 'Unexpected warnings: ${collector.warnings}'); |
| Expect.equals( |
| MessageKind.UNDEFINED_GETTER, collector.warnings.first.message.kind); |
| Expect.equals( |
| 0, collector.errors.length, 'Unexpected errors: ${collector.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length, |
| 'Unexpected warnings: ${collector.warnings}'); |
| Expect.equals( |
| 0, collector.errors.length, 'Unexpected errors: ${collector.errors}'); |
| }), |
| MockCompiler.create((MockCompiler compiler) { |
| compiler.parseScript("""enum Enum { A } |
| main() { new Enum(0, ''); }"""); |
| FunctionElement mainElement = compiler.mainApp.find(MAIN); |
| compiler.resolver.resolve(mainElement); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length, |
| 'Unexpected warnings: ${collector.warnings}'); |
| Expect.equals( |
| 1, collector.errors.length, 'Unexpected errors: ${collector.errors}'); |
| Expect.equals(MessageKind.CANNOT_INSTANTIATE_ENUM, |
| collector.errors.first.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); |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length, |
| 'Unexpected warnings: ${collector.warnings}'); |
| Expect.equals( |
| 1, collector.errors.length, 'Unexpected errors: ${collector.errors}'); |
| Expect.equals(MessageKind.CANNOT_INSTANTIATE_ENUM, |
| collector.errors.first.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]; |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(0, collector.errors.length); |
| List<ConstantExpression> constants = elements.constants; |
| String constantsText = |
| '[${constants.map((c) => c.toDartText()).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].toDartText(), |
| "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.run(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) { |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(0, collector.infos.length); |
| Expect.equals(1, collector.hints.length); |
| Expect.equals(MessageKind.OVERRIDE_EQUALS_NOT_HASH_CODE, |
| collector.hints.first.message.kind); |
| Expect.equals(0, collector.errors.length); |
| })); |
| } |
| |
| testConstConstructorAndNonFinalFields() { |
| void expect(compiler, List errors, List infos) { |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(errors.length, collector.errors.length); |
| for (int i = 0; i < errors.length; i++) { |
| Expect.equals(errors[i], collector.errors.elementAt(i).message.kind); |
| } |
| Expect.equals(0, collector.warnings.length); |
| Expect.equals(infos.length, collector.infos.length); |
| for (int i = 0; i < infos.length; i++) { |
| Expect.equals(infos[i], collector.infos.elementAt(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.UNDEFINED_SETTER]); |
| checkWarningOn( |
| ''' |
| main() { new B().bar(); } |
| class B { |
| mname() {} |
| bar() { |
| this.mname = () => null; |
| } |
| } |
| ''', |
| [MessageKind.UNDEFINED_SETTER]); |
| |
| // 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.UNDEFINED_SETTER |
| ]); |
| |
| // 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.UNDEFINED_STATIC_SETTER_BUT_GETTER]); |
| checkWarningOn( |
| ''' |
| main() { |
| const x = 1; |
| x = 2; |
| } |
| ''', |
| [MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER]); |
| checkWarningOn( |
| ''' |
| final x = 1; |
| main() { x = 3; } |
| ''', |
| [MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER]); |
| |
| checkWarningOn( |
| ''' |
| const x = 1; |
| main() { x = 3; } |
| ''', |
| [MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER]); |
| |
| // Detect assignments to final fields: |
| checkWarningOn( |
| ''' |
| main() => new B().m(); |
| class B { |
| final x = 1; |
| m() { x = 2; } |
| } |
| ''', |
| [MessageKind.UNDEFINED_SETTER]); |
| |
| // ... even if 'this' is explicit: |
| checkWarningOn( |
| ''' |
| main() => new B().m(); |
| class B { |
| final x = 1; |
| m() { this.x = 2; } |
| } |
| ''', |
| [MessageKind.UNDEFINED_SETTER]); |
| |
| // ... 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.UNDEFINED_SETTER |
| ]); |
| |
| // 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.UNDEFINED_SUPER_SETTER, |
| // TODO(johnniwinther): Avoid duplicate warnings. |
| MessageKind.UNDEFINED_SETTER |
| ]); |
| } |
| |
| /// 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) { |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.errors.length, |
| 'Unexpected errors in\n$script\n${collector.errors}'); |
| Expect.equals( |
| warnings.length, |
| collector.warnings.length, |
| 'Unexpected warnings in\n$script\n' |
| 'Expected:$warnings\nFound:${collector.warnings}'); |
| for (int i = 0; i < warnings.length; i++) { |
| Expect.equals( |
| warnings[i], collector.warnings.elementAt(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) { |
| DiagnosticCollector collector = compiler.diagnosticCollector; |
| Expect.equals(0, collector.errors.length); |
| Expect.equals(1, collector.warnings.length); |
| Expect.equals( |
| "$prefix.\n" |
| "Did you mean to add the 'async' marker to $where?", |
| '${collector.warnings.first.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'); |
| } |