blob: c9c0221a7da689839add710c6eab168287cfa3d9 [file] [log] [blame]
// Copyright (c) 2011, 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 '../../../sdk/lib/_internal/compiler/compiler.dart' as api;
import '../../../sdk/lib/_internal/compiler/implementation/elements/elements.dart';
import '../../../sdk/lib/_internal/compiler/implementation/tree/tree.dart';
import '../../../sdk/lib/_internal/compiler/implementation/util/util.dart';
import '../../../sdk/lib/_internal/compiler/implementation/source_file.dart';
import 'mock_compiler.dart';
import 'parser_helper.dart';
import '../../../sdk/lib/_internal/compiler/implementation/elements/modelx.dart'
show ElementX, CompilationUnitElementX;
import '../../../sdk/lib/_internal/compiler/implementation/dart2jslib.dart'
hide SourceString;
import '../../../sdk/lib/_internal/compiler/implementation/dart_types.dart';
DartType voidType;
DartType intType;
DartType boolType;
DartType stringType;
DartType doubleType;
DartType objectType;
main() {
List tests = [testSimpleTypes,
testReturn,
testFor,
testWhile,
testTry,
testSwitch,
testOperators,
testConstructorInvocationArgumentCount,
testConstructorInvocationArgumentTypes,
testMethodInvocationArgumentCount,
testMethodInvocations,
testControlFlow,
// testNewExpression,
testConditionalExpression,
testIfStatement,
testThis,
testSuper,
testOperatorsAssignability];
for (Function test in tests) {
setup();
test();
}
}
testSimpleTypes() {
Expect.equals(intType, analyzeType("3"));
Expect.equals(boolType, analyzeType("false"));
Expect.equals(boolType, analyzeType("true"));
Expect.equals(stringType, analyzeType("'hestfisk'"));
}
testReturn() {
analyzeTopLevel("void foo() { return 3; }", MessageKind.RETURN_VALUE_IN_VOID);
analyzeTopLevel("int bar() { return 'hest'; }", MessageKind.NOT_ASSIGNABLE);
analyzeTopLevel("void baz() { var x; return x; }");
analyzeTopLevel(returnWithType("int", "'string'"),
MessageKind.NOT_ASSIGNABLE);
analyzeTopLevel(returnWithType("", "'string'"));
analyzeTopLevel(returnWithType("Object", "'string'"));
analyzeTopLevel(returnWithType("String", "'string'"));
analyzeTopLevel(returnWithType("String", null));
analyzeTopLevel(returnWithType("int", null));
analyzeTopLevel(returnWithType("void", ""));
analyzeTopLevel(returnWithType("void", 1), MessageKind.RETURN_VALUE_IN_VOID);
analyzeTopLevel(returnWithType("void", null));
analyzeTopLevel(returnWithType("String", ""), MessageKind.RETURN_NOTHING);
// analyzeTopLevel("String foo() {};"); // Should probably fail.
}
testFor() {
analyze("for (var x;true;x = x + 1) {}");
analyze("for (var x;null;x = x + 1) {}");
analyze("for (var x;0;x = x + 1) {}", MessageKind.NOT_ASSIGNABLE);
analyze("for (var x;'';x = x + 1) {}", MessageKind.NOT_ASSIGNABLE);
analyze("for (;true;) {}");
analyze("for (;null;) {}");
analyze("for (;0;) {}", MessageKind.NOT_ASSIGNABLE);
analyze("for (;'';) {}", MessageKind.NOT_ASSIGNABLE);
// Foreach tests
// TODO(karlklose): for each is not yet implemented.
// analyze("{ List<String> strings = ['1','2','3']; " +
// "for (String s in strings) {} }");
// analyze("{ List<int> ints = [1,2,3]; for (String s in ints) {} }",
// MessageKind.NOT_ASSIGNABLE);
// analyze("for (String s in true) {}", MessageKind.METHOD_NOT_FOUND);
}
testWhile() {
analyze("while (true) {}");
analyze("while (null) {}");
analyze("while (0) {}", MessageKind.NOT_ASSIGNABLE);
analyze("while ('') {}", MessageKind.NOT_ASSIGNABLE);
analyze("do {} while (true);");
analyze("do {} while (null);");
analyze("do {} while (0);", MessageKind.NOT_ASSIGNABLE);
analyze("do {} while ('');", MessageKind.NOT_ASSIGNABLE);
analyze("do { int i = 0.5; } while (true);", MessageKind.NOT_ASSIGNABLE);
analyze("do { int i = 0.5; } while (null);", MessageKind.NOT_ASSIGNABLE);
}
testTry() {
analyze("try {} finally {}");
analyze("try {} catch (e) { int i = e;} finally {}");
analyze("try {} catch (e, s) { int i = e; StackTrace j = s; } finally {}");
analyze("try {} on String catch (e) {} finally {}");
analyze("try { int i = ''; } finally {}", MessageKind.NOT_ASSIGNABLE);
analyze("try {} finally { int i = ''; }", MessageKind.NOT_ASSIGNABLE);
analyze("try {} on String catch (e) { int i = e; } finally {}",
MessageKind.NOT_ASSIGNABLE);
analyze("try {} catch (e, s) { int i = e; int j = s; } finally {}",
MessageKind.NOT_ASSIGNABLE);
analyze("try {} on String catch (e, s) { int i = e; int j = s; } finally {}",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
}
testSwitch() {
analyze("switch (0) { case 1: break; case 2: break; }");
analyze("switch (0) { case 1: int i = ''; break; case 2: break; }",
MessageKind.NOT_ASSIGNABLE);
analyze("switch (0) { case '': break; case 2: break; }",
MessageKind.NOT_ASSIGNABLE);
analyze("switch ('') { case 1: break; case 2: break; }",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
}
testOperators() {
// TODO(karlklose): add the DartC tests for operators when we can parse
// classes with operators.
for (final op in ['+', '-', '*', '/', '%', '~/', '|', '&']) {
analyze("{ var i = 1 ${op} 2; }");
analyze("{ var i = 1; i ${op}= 2; }");
analyze("{ int i; var j = (i = true) ${op} 2; }",
[MessageKind.NOT_ASSIGNABLE, MessageKind.OPERATOR_NOT_FOUND]);
analyze("{ int i; var j = 1 ${op} (i = true); }",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
}
for (final op in ['-', '~']) {
analyze("{ var i = ${op}1; }");
analyze("{ int i; var j = ${op}(i = true); }",
[MessageKind.NOT_ASSIGNABLE, MessageKind.OPERATOR_NOT_FOUND]);
}
for (final op in ['++', '--']) {
analyze("{ int i = 1; int j = i${op}; }");
analyze("{ int i = 1; bool j = i${op}; }", MessageKind.NOT_ASSIGNABLE);
analyze("{ bool b = true; bool j = b${op}; }",
MessageKind.OPERATOR_NOT_FOUND);
analyze("{ bool b = true; int j = ${op}b; }",
MessageKind.OPERATOR_NOT_FOUND);
}
for (final op in ['||', '&&']) {
analyze("{ bool b = (true ${op} false); }");
analyze("{ int b = true ${op} false; }", MessageKind.NOT_ASSIGNABLE);
analyze("{ bool b = (1 ${op} false); }", MessageKind.NOT_ASSIGNABLE);
analyze("{ bool b = (true ${op} 2); }", MessageKind.NOT_ASSIGNABLE);
}
for (final op in ['>', '<', '<=', '>=']) {
analyze("{ bool b = 1 ${op} 2; }");
analyze("{ int i = 1 ${op} 2; }", MessageKind.NOT_ASSIGNABLE);
analyze("{ int i; bool b = (i = true) ${op} 2; }",
[MessageKind.NOT_ASSIGNABLE, MessageKind.OPERATOR_NOT_FOUND]);
analyze("{ int i; bool b = 1 ${op} (i = true); }",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
}
for (final op in ['==', '!=']) {
analyze("{ bool b = 1 ${op} 2; }");
analyze("{ int i = 1 ${op} 2; }", MessageKind.NOT_ASSIGNABLE);
analyze("{ int i; bool b = (i = true) ${op} 2; }",
MessageKind.NOT_ASSIGNABLE);
analyze("{ int i; bool b = 1 ${op} (i = true); }",
MessageKind.NOT_ASSIGNABLE);
}
}
void testConstructorInvocationArgumentCount() {
compiler.parseScript("""
class C1 { C1(x, y); }
class C2 { C2(int x, int y); }
""");
// calls to untyped constructor C1
analyze("new C1(1, 2);");
analyze("new C1();", MessageKind.MISSING_ARGUMENT);
analyze("new C1(1);", MessageKind.MISSING_ARGUMENT);
analyze("new C1(1, 2, 3);", MessageKind.ADDITIONAL_ARGUMENT);
// calls to typed constructor C2
analyze("new C2(1, 2);");
analyze("new C2();", MessageKind.MISSING_ARGUMENT);
analyze("new C2(1);", MessageKind.MISSING_ARGUMENT);
analyze("new C2(1, 2, 3);", MessageKind.ADDITIONAL_ARGUMENT);
}
void testConstructorInvocationArgumentTypes() {
compiler.parseScript("""
class C1 { C1(x); }
class C2 { C2(int x); }
""");
analyze("new C1(42);");
analyze("new C1('string');");
analyze("new C2(42);");
analyze("new C2('string');",
MessageKind.NOT_ASSIGNABLE);
}
void testMethodInvocationArgumentCount() {
compiler.parseScript(CLASS_WITH_METHODS);
check(String text, [expectedWarnings]) {
analyze("{ ClassWithMethods c; $text }", expectedWarnings);
}
check("c.untypedNoArgumentMethod(1);", MessageKind.ADDITIONAL_ARGUMENT);
check("c.untypedOneArgumentMethod();", MessageKind.MISSING_ARGUMENT);
check("c.untypedOneArgumentMethod(1, 1);", MessageKind.ADDITIONAL_ARGUMENT);
check("c.untypedTwoArgumentMethod();", MessageKind.MISSING_ARGUMENT);
check("c.untypedTwoArgumentMethod(1, 2, 3);",
MessageKind.ADDITIONAL_ARGUMENT);
check("c.intNoArgumentMethod(1);", MessageKind.ADDITIONAL_ARGUMENT);
check("c.intOneArgumentMethod();", MessageKind.MISSING_ARGUMENT);
check("c.intOneArgumentMethod(1, 1);", MessageKind.ADDITIONAL_ARGUMENT);
check("c.intTwoArgumentMethod();", MessageKind.MISSING_ARGUMENT);
check("c.intTwoArgumentMethod(1, 2, 3);", MessageKind.ADDITIONAL_ARGUMENT);
// check("c.untypedField();");
check("c.intOneArgumentOneOptionalMethod();", [MessageKind.MISSING_ARGUMENT]);
check("c.intOneArgumentOneOptionalMethod(0);");
check("c.intOneArgumentOneOptionalMethod(0, 1);");
check("c.intOneArgumentOneOptionalMethod(0, 1, 2);",
[MessageKind.ADDITIONAL_ARGUMENT]);
check("c.intOneArgumentOneOptionalMethod(0, 1, c: 2);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intOneArgumentOneOptionalMethod(0, b: 1);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intOneArgumentOneOptionalMethod(a: 0, b: 1);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND,
MessageKind.NAMED_ARGUMENT_NOT_FOUND,
MessageKind.MISSING_ARGUMENT]);
check("c.intTwoOptionalMethod();");
check("c.intTwoOptionalMethod(0);");
check("c.intTwoOptionalMethod(0, 1);");
check("c.intTwoOptionalMethod(0, 1, 2);", [MessageKind.ADDITIONAL_ARGUMENT]);
check("c.intTwoOptionalMethod(a: 0);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intTwoOptionalMethod(0, b: 1);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intOneArgumentOneNamedMethod();", [MessageKind.MISSING_ARGUMENT]);
check("c.intOneArgumentOneNamedMethod(0);");
check("c.intOneArgumentOneNamedMethod(0, b: 1);");
check("c.intOneArgumentOneNamedMethod(b: 1);",
[MessageKind.MISSING_ARGUMENT]);
check("c.intOneArgumentOneNamedMethod(0, b: 1, c: 2);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intOneArgumentOneNamedMethod(0, 1);",
[MessageKind.ADDITIONAL_ARGUMENT]);
check("c.intOneArgumentOneNamedMethod(0, 1, c: 2);",
[MessageKind.ADDITIONAL_ARGUMENT,
MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intOneArgumentOneNamedMethod(a: 1, b: 1);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND,
MessageKind.MISSING_ARGUMENT]);
check("c.intTwoNamedMethod();");
check("c.intTwoNamedMethod(a: 0);");
check("c.intTwoNamedMethod(b: 1);");
check("c.intTwoNamedMethod(a: 0, b: 1);");
check("c.intTwoNamedMethod(b: 1, a: 0);");
check("c.intTwoNamedMethod(0);", [MessageKind.ADDITIONAL_ARGUMENT]);
check("c.intTwoNamedMethod(c: 2);", [MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intTwoNamedMethod(a: 0, c: 2);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intTwoNamedMethod(a: 0, b: 1, c: 2);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intTwoNamedMethod(c: 2, b: 1, a: 0);",
[MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
check("c.intTwoNamedMethod(0, b: 1);", [MessageKind.ADDITIONAL_ARGUMENT]);
check("c.intTwoNamedMethod(0, 1);",
[MessageKind.ADDITIONAL_ARGUMENT,
MessageKind.ADDITIONAL_ARGUMENT]);
check("c.intTwoNamedMethod(0, c: 2);",
[MessageKind.ADDITIONAL_ARGUMENT,
MessageKind.NAMED_ARGUMENT_NOT_FOUND]);
}
void testMethodInvocations() {
compiler.parseScript(CLASS_WITH_METHODS);
check(String text, [expectedWarnings]){
analyze("""{
ClassWithMethods c;
SubClass d;
var e;
int i;
int j;
int localMethod(String str) { return 0; }
$text
}
""", expectedWarnings);
}
check("int k = c.untypedNoArgumentMethod();");
check("ClassWithMethods x = c.untypedNoArgumentMethod();");
check("ClassWithMethods x = d.untypedNoArgumentMethod();");
check("int k = d.intMethod();");
check("int k = c.untypedOneArgumentMethod(c);");
check("ClassWithMethods x = c.untypedOneArgumentMethod(1);");
check("int k = c.untypedOneArgumentMethod('string');");
check("int k = c.untypedOneArgumentMethod(i);");
check("int k = d.untypedOneArgumentMethod(d);");
check("ClassWithMethods x = d.untypedOneArgumentMethod(1);");
check("int k = d.untypedOneArgumentMethod('string');");
check("int k = d.untypedOneArgumentMethod(i);");
check("int k = c.untypedTwoArgumentMethod(1, 'string');");
check("int k = c.untypedTwoArgumentMethod(i, j);");
check("ClassWithMethods x = c.untypedTwoArgumentMethod(i, c);");
check("int k = d.untypedTwoArgumentMethod(1, 'string');");
check("int k = d.untypedTwoArgumentMethod(i, j);");
check("ClassWithMethods x = d.untypedTwoArgumentMethod(i, d);");
check("int k = c.intNoArgumentMethod();");
check("ClassWithMethods x = c.intNoArgumentMethod();",
MessageKind.NOT_ASSIGNABLE);
check("int k = c.intOneArgumentMethod(c);", MessageKind.NOT_ASSIGNABLE);
check("ClassWithMethods x = c.intOneArgumentMethod(1);",
MessageKind.NOT_ASSIGNABLE);
check("int k = c.intOneArgumentMethod('string');",
MessageKind.NOT_ASSIGNABLE);
check("int k = c.intOneArgumentMethod(i);");
check("int k = c.intTwoArgumentMethod(1, 'string');",
MessageKind.NOT_ASSIGNABLE);
check("int k = c.intTwoArgumentMethod(i, j);");
check("ClassWithMethods x = c.intTwoArgumentMethod(i, j);",
MessageKind.NOT_ASSIGNABLE);
check("c.functionField();");
check("d.functionField();");
check("c.functionField(1);");
check("d.functionField('string');");
check("c.intField();", MessageKind.NOT_CALLABLE);
check("d.intField();", MessageKind.NOT_CALLABLE);
check("c.untypedField();");
check("d.untypedField();");
check("c.untypedField(1);");
check("d.untypedField('string');");
check("c.intOneArgumentOneOptionalMethod('');",
MessageKind.NOT_ASSIGNABLE);
check("c.intOneArgumentOneOptionalMethod('', '');",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
check("c.intTwoOptionalMethod('');", MessageKind.NOT_ASSIGNABLE);
check("c.intTwoOptionalMethod('', '');",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
check("c.intOneArgumentOneNamedMethod('');",
MessageKind.NOT_ASSIGNABLE);
check("c.intOneArgumentOneNamedMethod('', b: '');",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
check("c.intTwoNamedMethod(a: '');", MessageKind.NOT_ASSIGNABLE);
check("c.intTwoNamedMethod(b: '');", MessageKind.NOT_ASSIGNABLE);
check("c.intTwoNamedMethod(a: '', b: '');",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
check("c.intTwoNamedMethod(b: '', a: '');",
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
// Invocation of dynamic variable.
check("e();");
check("e(1);");
check("e('string');");
// Invocation on local method.
check("localMethod();", MessageKind.MISSING_ARGUMENT);
check("localMethod(1);", MessageKind.NOT_ASSIGNABLE);
check("localMethod('string');");
check("int k = localMethod('string');");
check("String k = localMethod('string');", MessageKind.NOT_ASSIGNABLE);
// Invocation on parenthesized expressions.
check("(e)();");
check("(e)(1);");
check("(e)('string');");
check("(foo)();");
check("(foo)(1);");
check("(foo)('string');");
// Invocations on function expressions.
check("(foo){}();", MessageKind.MISSING_ARGUMENT);
check("(foo){}(1);");
check("(foo){}('string');");
check("(int foo){}('string');", MessageKind.NOT_ASSIGNABLE);
check("(String foo){}('string');");
check("int k = int bar(String foo){ return 0; }('string');");
check("int k = String bar(String foo){ return foo; }('string');",
MessageKind.NOT_ASSIGNABLE);
// Static invocations.
check("ClassWithMethods.staticMethod();",
MessageKind.MISSING_ARGUMENT);
check("ClassWithMethods.staticMethod(1);",
MessageKind.NOT_ASSIGNABLE);
check("ClassWithMethods.staticMethod('string');");
check("int k = ClassWithMethods.staticMethod('string');");
check("String k = ClassWithMethods.staticMethod('string');",
MessageKind.NOT_ASSIGNABLE);
// Invocation on dynamic variable.
check("e.foo();");
check("e.foo(1);");
check("e.foo('string');");
// Invocation on unresolved variable.
check("foo();");
check("foo(1);");
check("foo('string');");
check("foo(a: 'string');");
check("foo(a: localMethod(1));", MessageKind.NOT_ASSIGNABLE);
// TODO(johnniwinther): Add tests of invocations using implicit this.
}
/** Tests analysis of returns (not required by the specification). */
void testControlFlow() {
analyzeTopLevel("void foo() { if (true) { return; } }");
analyzeTopLevel("foo() { if (true) { return; } }");
analyzeTopLevel("int foo() { if (true) { return 1; } }",
MessageKind.MAYBE_MISSING_RETURN);
final bar =
"""void bar() {
if (true) {
if (true) { return; } else { return; }
} else { return; }
}""";
analyzeTopLevel(bar);
analyzeTopLevel("void baz() { return; int i = 1; }",
MessageKind.UNREACHABLE_CODE);
final qux =
"""void qux() {
if (true) {
return;
} else if (true) {
if (true) {
return;
}
throw 'hest';
}
throw 'fisk';
}""";
analyzeTopLevel(qux);
analyzeTopLevel("int hest() {}", MessageKind.MISSING_RETURN);
final fisk = """int fisk() {
if (true) {
if (true) { return 1; } else {}
} else { return 1; }
}""";
analyzeTopLevel(fisk, MessageKind.MAYBE_MISSING_RETURN);
analyzeTopLevel("int foo() { while(true) { return 1; } }");
analyzeTopLevel("int foo() { while(true) { return 1; } return 2; }",
MessageKind.UNREACHABLE_CODE);
}
testNewExpression() {
compiler.parseScript("class A {}");
analyze("A a = new A();");
analyze("int i = new A();", MessageKind.NOT_ASSIGNABLE);
// TODO(karlklose): constructors are not yet implemented.
// compiler.parseScript(
// "class Foo {\n" +
// " Foo(int x) {}\n" +
// " Foo.foo() {}\n" +
// " Foo.bar([int i = null]) {}\n" +
// "}\n" +
// "abstract class Bar<T> {\n" +
// " factory Bar.make() => new Baz<T>.make();\n" +
// "}\n" +
// "class Baz {\n" +
// " factory Bar<S>.make(S x) { return null; }\n" +
// "}");
//
// analyze("Foo x = new Foo(0);");
// analyze("Foo x = new Foo();", MessageKind.MISSING_ARGUMENT);
// analyze("Foo x = new Foo('');", MessageKind.NOT_ASSIGNABLE);
// analyze("Foo x = new Foo(0, null);", MessageKind.ADDITIONAL_ARGUMENT);
//
// analyze("Foo x = new Foo.foo();");
// analyze("Foo x = new Foo.foo(null);", MessageKind.ADDITIONAL_ARGUMENT);
//
// analyze("Foo x = new Foo.bar();");
// analyze("Foo x = new Foo.bar(0);");
// analyze("Foo x = new Foo.bar('');", MessageKind.NOT_ASSIGNABLE);
// analyze("Foo x = new Foo.bar(0, null);",
// MessageKind.ADDITIONAL_ARGUMENT);
//
// analyze("Bar<String> x = new Bar<String>.make('');");
}
testConditionalExpression() {
analyze("int i = true ? 2 : 1;");
analyze("int i = true ? 'hest' : 1;");
analyze("int i = true ? 'hest' : 'fisk';", MessageKind.NOT_ASSIGNABLE);
analyze("String s = true ? 'hest' : 'fisk';");
analyze("true ? 1 : 2;");
analyze("null ? 1 : 2;");
analyze("0 ? 1 : 2;", MessageKind.NOT_ASSIGNABLE);
analyze("'' ? 1 : 2;", MessageKind.NOT_ASSIGNABLE);
analyze("{ int i; true ? i = 2.7 : 2; }",
MessageKind.NOT_ASSIGNABLE);
analyze("{ int i; true ? 2 : i = 2.7; }",
MessageKind.NOT_ASSIGNABLE);
analyze("{ int i; i = true ? 2.7 : 2; }");
}
testIfStatement() {
analyze("if (true) {}");
analyze("if (null) {}");
analyze("if (0) {}",
MessageKind.NOT_ASSIGNABLE);
analyze("if ('') {}",
MessageKind.NOT_ASSIGNABLE);
analyze("{ int i = 27; if (true) { i = 2.7; } else {} }",
MessageKind.NOT_ASSIGNABLE);
analyze("{ int i = 27; if (true) {} else { i = 2.7; } }",
MessageKind.NOT_ASSIGNABLE);
}
testThis() {
String script = "class Foo {}";
LibraryElement library = mockLibrary(compiler, script);
compiler.parseScript(script, library);
ClassElement foo = library.find(const SourceString("Foo"));
analyzeIn(foo, "{ int i = this; }", MessageKind.NOT_ASSIGNABLE);
analyzeIn(foo, "{ Object o = this; }");
analyzeIn(foo, "{ Foo f = this; }");
}
testSuper() {
String script = r'''
class A {
String field = "42";
}
class B extends A {
Object field = 42;
}
''';
LibraryElement library = mockLibrary(compiler, script);
compiler.parseScript(script, library);
ClassElement B = library.find(const SourceString("B"));
analyzeIn(B, "{ int i = super.field; }", MessageKind.NOT_ASSIGNABLE);
analyzeIn(B, "{ Object o = super.field; }");
analyzeIn(B, "{ String s = super.field; }");
}
const String CLASSES_WITH_OPERATORS = '''
class Operators {
Operators operator +(Operators other) => this;
Operators operator -(Operators other) => this;
Operators operator -() => this;
Operators operator *(Operators other) => this;
Operators operator /(Operators other) => this;
Operators operator %(Operators other) => this;
Operators operator ~/(Operators other) => this;
Operators operator &(Operators other) => this;
Operators operator |(Operators other) => this;
Operators operator ^(Operators other) => this;
Operators operator ~() => this;
Operators operator <(Operators other) => true;
Operators operator >(Operators other) => false;
Operators operator <=(Operators other) => this;
Operators operator >=(Operators other) => this;
Operators operator <<(Operators other) => this;
Operators operator >>(Operators other) => this;
bool operator ==(Operators other) => true;
Operators operator [](Operators key) => this;
void operator []=(Operators key, Operators value) {}
}
class MismatchA {
int operator+(MismatchA other) => 0;
MismatchA operator-(int other) => this;
MismatchA operator[](int key) => this;
void operator[]=(int key, MismatchA value) {}
}
class MismatchB {
MismatchB operator+(MismatchB other) => this;
MismatchB operator[](int key) => this;
void operator[]=(String key, MismatchB value) {}
}
class MismatchC {
MismatchC operator+(MismatchC other) => this;
MismatchC operator[](int key) => this;
void operator[]=(int key, String value) {}
}
''';
testOperatorsAssignability() {
compiler.parseScript(CLASSES_WITH_OPERATORS);
// Tests against Operators.
String header = """{
bool z;
Operators a;
Operators b;
Operators c;
""";
check(String text, [expectedWarnings]) {
analyze('$header $text }', expectedWarnings);
}
// Positive tests on operators.
check('c = a + b;');
check('c = a - b;');
check('c = -a;');
check('c = a * b;');
check('c = a / b;');
check('c = a % b;');
check('c = a ~/ b;');
check('c = a & b;');
check('c = a | b;');
check('c = a ^ b;');
check('c = ~a;');
check('c = a < b;');
check('c = a > b;');
check('c = a <= b;');
check('c = a >= b;');
check('c = a << b;');
check('c = a >> b;');
check('c = a[b];');
check('a[b] = c;');
check('a[b] += c;');
check('a[b] -= c;');
check('a[b] *= c;');
check('a[b] /= c;');
check('a[b] %= c;');
check('a[b] ~/= c;');
check('a[b] <<= c;');
check('a[b] >>= c;');
check('a[b] &= c;');
check('a[b] |= c;');
check('a[b] ^= c;');
check('a += b;');
check('a -= b;');
check('a *= b;');
check('a /= b;');
check('a %= b;');
check('a ~/= b;');
check('a <<= b;');
check('a >>= b;');
check('a &= b;');
check('a |= b;');
check('a ^= b;');
// Negative tests on operators.
// For the sake of brevity we misuse the terminology in comments:
// 'e1 is not assignable to e2' should be read as
// 'the type of e1 is not assignable to the type of e2', and
// 'e1 is not assignable to operator o on e2' should be read as
// 'the type of e1 is not assignable to the argument type of operator o
// on e2'.
// `0` is not assignable to operator + on `a`.
check('c = a + 0;', MessageKind.NOT_ASSIGNABLE);
// `a + b` is not assignable to `z`.
check('z = a + b;', MessageKind.NOT_ASSIGNABLE);
// `-a` is not assignable to `z`.
check('z = -a;', MessageKind.NOT_ASSIGNABLE);
// `0` is not assignable to operator [] on `a`.
check('c = a[0];', MessageKind.NOT_ASSIGNABLE);
// `a[b]` is not assignable to `z`.
check('z = a[b];', MessageKind.NOT_ASSIGNABLE);
// `0` is not assignable to operator [] on `a`.
// Warning suppressed for `0` is not assignable to operator []= on `a`.
check('a[0] *= c;', MessageKind.NOT_ASSIGNABLE);
// `z` is not assignable to operator * on `a[0]`.
check('a[b] *= z;', MessageKind.NOT_ASSIGNABLE);
check('b = a++;', MessageKind.NOT_ASSIGNABLE);
check('b = ++a;', MessageKind.NOT_ASSIGNABLE);
check('b = a--;', MessageKind.NOT_ASSIGNABLE);
check('b = --a;', MessageKind.NOT_ASSIGNABLE);
check('c = a[b]++;', MessageKind.NOT_ASSIGNABLE);
check('c = ++a[b];', MessageKind.NOT_ASSIGNABLE);
check('c = a[b]--;', MessageKind.NOT_ASSIGNABLE);
check('c = --a[b];', MessageKind.NOT_ASSIGNABLE);
check('z = a == b;');
check('z = a != b;');
for (String o in ['&&', '||']) {
check('z = z $o z;');
check('z = a $o z;', MessageKind.NOT_ASSIGNABLE);
check('z = z $o b;', MessageKind.NOT_ASSIGNABLE);
check('z = a $o b;',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
check('a = a $o b;',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE,
MessageKind.NOT_ASSIGNABLE]);
}
check('z = !z;');
check('z = !a;', MessageKind.NOT_ASSIGNABLE);
check('a = !z;', MessageKind.NOT_ASSIGNABLE);
check('a = !a;',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
// Tests against MismatchA.
header = """{
MismatchA a;
MismatchA b;
MismatchA c;
""";
// Tests against int operator +(MismatchA other) => 0;
// `a + b` is not assignable to `c`.
check('c = a + b;', MessageKind.NOT_ASSIGNABLE);
// `a + b` is not assignable to `a`.
check('a += b;', MessageKind.NOT_ASSIGNABLE);
// `a[0] + b` is not assignable to `a[0]`.
check('a[0] += b;', MessageKind.NOT_ASSIGNABLE);
// 1 is not applicable to operator +.
check('b = a++;', MessageKind.NOT_ASSIGNABLE);
// 1 is not applicable to operator +.
// `++a` of type int is not assignable to `b`.
check('b = ++a;',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
// 1 is not applicable to operator +.
check('b = a[0]++;', MessageKind.NOT_ASSIGNABLE);
// 1 is not applicable to operator +.
// `++a[0]` of type int is not assignable to `b`.
check('b = ++a[0];',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
// Tests against: MismatchA operator -(int other) => this;
// `a - b` is not assignable to `c`.
check('c = a + b;', MessageKind.NOT_ASSIGNABLE);
// `a - b` is not assignable to `a`.
check('a += b;', MessageKind.NOT_ASSIGNABLE);
// `a[0] - b` is not assignable to `a[0]`.
check('a[0] += b;', MessageKind.NOT_ASSIGNABLE);
check('b = a--;');
check('b = --a;');
check('b = a[0]--;');
check('b = --a[0];');
// Tests against MismatchB.
header = """{
MismatchB a;
MismatchB b;
MismatchB c;
""";
// Tests against:
// MismatchB operator [](int key) => this;
// void operator []=(String key, MismatchB value) {}
// `0` is not applicable to operator []= on `a`.
check('a[0] = b;', MessageKind.NOT_ASSIGNABLE);
// `0` is not applicable to operator []= on `a`.
check('a[0] += b;', MessageKind.NOT_ASSIGNABLE);
// `""` is not applicable to operator [] on `a`.
check('a[""] += b;', MessageKind.NOT_ASSIGNABLE);
// `c` is not applicable to operator [] on `a`.
// `c` is not applicable to operator []= on `a`.
check('a[c] += b;',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
// Tests against MismatchB.
header = """{
MismatchC a;
MismatchC b;
MismatchC c;
""";
// Tests against:
// MismatchC operator[](int key) => this;
// void operator[]=(int key, String value) {}
// `b` is not assignable to `a[0]`.
check('a[0] += b;', MessageKind.NOT_ASSIGNABLE);
// `0` is not applicable to operator + on `a[0]`.
check('a[0] += "";',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
// `true` is not applicable to operator + on `a[0]`.
// `true` is not assignable to `a[0]`.
check('a[0] += true;',
[MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]);
}
const CLASS_WITH_METHODS = '''
class ClassWithMethods {
untypedNoArgumentMethod() {}
untypedOneArgumentMethod(argument) {}
untypedTwoArgumentMethod(argument1, argument2) {}
int intNoArgumentMethod() {}
int intOneArgumentMethod(int argument) {}
int intTwoArgumentMethod(int argument1, int argument2) {}
void intOneArgumentOneOptionalMethod(int a, [int b]) {}
void intTwoOptionalMethod([int a, int b]) {}
void intOneArgumentOneNamedMethod(int a, {int b}) {}
void intTwoNamedMethod({int a, int b}) {}
Function functionField;
var untypedField;
int intField;
static int staticMethod(String str) {}
}
class I {
int intMethod();
}
class SubClass extends ClassWithMethods implements I {}''';
Types types;
MockCompiler compiler;
String returnWithType(String type, expression) {
return "$type foo() { return $expression; }";
}
Node parseExpression(String text) =>
parseBodyCode(text, (parser, token) => parser.parseExpression(token));
const String NUM_SOURCE = '''
abstract class num {
num operator +(num other);
num operator -(num other);
num operator *(num other);
num operator %(num other);
double operator /(num other);
int operator ~/(num other);
num operator -();
bool operator <(num other);
bool operator <=(num other);
bool operator >(num other);
bool operator >=(num other);
}
''';
const String INT_SOURCE = '''
abstract class int extends num {
int operator &(int other);
int operator |(int other);
int operator ^(int other);
int operator ~();
int operator <<(int shiftAmount);
int operator >>(int shiftAmount);
int operator -();
}
''';
void setup() {
RegExp classNum = new RegExp(r'abstract class num {}');
Expect.isTrue(DEFAULT_CORELIB.contains(classNum));
RegExp classInt = new RegExp(r'abstract class int extends num { }');
Expect.isTrue(DEFAULT_CORELIB.contains(classInt));
String CORE_SOURCE = DEFAULT_CORELIB
.replaceAll(classNum, NUM_SOURCE)
.replaceAll(classInt, INT_SOURCE);
compiler = new MockCompiler(coreSource: CORE_SOURCE);
types = compiler.types;
voidType = compiler.types.voidType;
intType = compiler.intClass.computeType(compiler);
doubleType = compiler.doubleClass.computeType(compiler);
boolType = compiler.boolClass.computeType(compiler);
stringType = compiler.stringClass.computeType(compiler);
objectType = compiler.objectClass.computeType(compiler);
}
DartType analyzeType(String text) {
var node = parseExpression(text);
TypeCheckerVisitor visitor =
new TypeCheckerVisitor(compiler, new TreeElementMapping(null), types);
return visitor.analyze(node);
}
analyzeTopLevel(String text, [expectedWarnings]) {
if (expectedWarnings == null) expectedWarnings = [];
if (expectedWarnings is !List) expectedWarnings = [expectedWarnings];
LibraryElement library = mockLibrary(compiler, text);
Link<Element> topLevelElements = parseUnit(text, compiler, library);
for (Link<Element> elements = topLevelElements;
!elements.isEmpty;
elements = elements.tail) {
Node node = elements.head.parseNode(compiler);
TreeElements mapping = compiler.resolver.resolve(elements.head);
TypeCheckerVisitor checker =
new TypeCheckerVisitor(compiler, mapping, types);
compiler.clearWarnings();
checker.analyze(node);
compareWarningKinds(text, expectedWarnings, compiler.warnings);
}
}
api.DiagnosticHandler createHandler(String text) {
return (uri, int begin, int end, String message, kind) {
SourceFile sourceFile;
if (uri == null) {
sourceFile = new SourceFile('analysis', text);
} else {
sourceFile = compiler.sourceFiles[uri.toString()];
}
if (sourceFile != null) {
print(sourceFile.getLocationMessage(message, begin, end, true, (x) => x));
} else {
print(message);
}
};
}
analyze(String text, [expectedWarnings]) {
if (expectedWarnings == null) expectedWarnings = [];
if (expectedWarnings is !List) expectedWarnings = [expectedWarnings];
compiler.diagnosticHandler = createHandler(text);
Token tokens = scan(text);
NodeListener listener = new NodeListener(compiler, null);
Parser parser = new Parser(listener);
parser.parseStatement(tokens);
Node node = listener.popNode();
Element compilationUnit =
new CompilationUnitElementX(new Script(null, null), compiler.mainApp);
Element function = new ElementX(
buildSourceString(''), ElementKind.FUNCTION, compilationUnit);
TreeElements elements = compiler.resolveNodeStatement(node, function);
TypeCheckerVisitor checker = new TypeCheckerVisitor(compiler, elements,
types);
compiler.clearWarnings();
checker.analyze(node);
compareWarningKinds(text, expectedWarnings, compiler.warnings);
compiler.diagnosticHandler = null;
}
void generateOutput(String text) {
for (WarningMessage message in compiler.warnings) {
var beginToken = message.node.getBeginToken();
var endToken = message.node.getEndToken();
int begin = beginToken.charOffset;
int end = endToken.charOffset + endToken.slowCharCount;
SourceFile sourceFile = new SourceFile('analysis', text);
print(sourceFile.getLocationMessage(message.message.toString(),
begin, end, true, (str) => str));
}
}
analyzeIn(ClassElement classElement, String text, [expectedWarnings]) {
if (expectedWarnings == null) expectedWarnings = [];
if (expectedWarnings is !List) expectedWarnings = [expectedWarnings];
Token tokens = scan(text);
NodeListener listener = new NodeListener(compiler, null);
Parser parser = new Parser(listener);
parser.parseStatement(tokens);
Node node = listener.popNode();
classElement.ensureResolved(compiler);
TreeElements elements = compiler.resolveNodeStatement(node, classElement);
TypeCheckerVisitor checker = new TypeCheckerVisitor(compiler, elements,
types);
compiler.clearWarnings();
checker.analyze(node);
generateOutput(text);
compareWarningKinds(text, expectedWarnings, compiler.warnings);
}