Index: tests/compiler/dart2js/type_checker_test.dart |
diff --git a/tests/compiler/dart2js/type_checker_test.dart b/tests/compiler/dart2js/type_checker_test.dart |
index 1381be8a03a56f82fbae12d4f2a28dc5ac82f137..46a162104e19fa116038bb1aa391c3acb584e4b2 100644 |
--- a/tests/compiler/dart2js/type_checker_test.dart |
+++ b/tests/compiler/dart2js/type_checker_test.dart |
@@ -3,14 +3,16 @@ |
// 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; |
+ show ElementX, CompilationUnitElementX; |
import '../../../sdk/lib/_internal/compiler/implementation/dart2jslib.dart' |
hide SourceString; |
@@ -38,7 +40,8 @@ main() { |
// testNewExpression, |
testConditionalExpression, |
testIfStatement, |
- testThis]; |
+ testThis, |
+ testOperatorsAssignability]; |
for (Function test in tests) { |
setup(); |
test(); |
@@ -111,19 +114,22 @@ testOperators() { |
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.NOT_ASSIGNABLE, MessageKind.OPERATOR_NOT_FOUND]); |
analyze("{ int i; var j = 1 ${op} (i = true); }", |
- MessageKind.NOT_ASSIGNABLE); |
+ [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); |
+ 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}; }"); |
- analyze("{ bool b = true; int j = ${op}b; }"); |
+ 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); }"); |
@@ -131,13 +137,21 @@ testOperators() { |
analyze("{ bool b = (1 ${op} false); }", MessageKind.NOT_ASSIGNABLE); |
analyze("{ bool b = (true ${op} 2); }", MessageKind.NOT_ASSIGNABLE); |
} |
- for (final op in ['>', '<', '<=', '>=', '==', '!=']) { |
+ 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.NOT_ASSIGNABLE, MessageKind.OPERATOR_NOT_FOUND]); |
analyze("{ int i; bool b = 1 ${op} (i = true); }", |
- MessageKind.NOT_ASSIGNABLE); |
+ [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); |
} |
} |
@@ -495,6 +509,277 @@ testThis() { |
analyzeIn(foo, "{ Foo f = this; }"); |
} |
+const String CLASSES_WITH_OPERATORS = ''' |
+class Operators { |
+ Operators operator+(Operators other) => this; |
karlklose
2013/05/17 09:36:53
Add a space between 'operator' and the symbol.
Johnni Winther
2013/05/17 11:46:33
Done.
|
+ 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; |
karlklose
2013/05/17 09:36:53
It would be nice if the name 'b' was available for
|
+ Operators a = new Operators(); |
karlklose
2013/05/17 09:36:53
Why do we need the 'new Operator()'?
Johnni Winther
2013/05/17 11:46:33
We don't.
|
+ Operators b = new Operators(); |
+ Operators c; |
+ """; |
+ |
+ // Positive tests on operators. |
+ |
+ analyze('$header c = a + b; }'); |
karlklose
2013/05/17 09:36:53
Perhaps add a local function
check(text) => anal
Johnni Winther
2013/05/17 11:46:33
Done.
|
+ analyze('$header c = a - b; }'); |
+ analyze('$header c = -a; }'); |
+ analyze('$header c = a * b; }'); |
+ analyze('$header c = a / b; }'); |
+ analyze('$header c = a % b; }'); |
+ analyze('$header c = a ~/ b; }'); |
+ |
+ analyze('$header c = a & b; }'); |
+ analyze('$header c = a | b; }'); |
+ analyze('$header c = a ^ b; }'); |
+ |
+ analyze('$header c = ~a; }'); |
+ |
+ analyze('$header c = a < b; }'); |
+ analyze('$header c = a > b; }'); |
+ analyze('$header c = a <= b; }'); |
+ analyze('$header c = a >= b; }'); |
+ |
+ analyze('$header c = a << b; }'); |
+ analyze('$header c = a >> b; }'); |
+ |
+ analyze('$header c = a[b]; }'); |
+ |
+ analyze('$header a[b] = c; }'); |
+ analyze('$header a[b] += c; }'); |
+ analyze('$header a[b] -= c; }'); |
+ analyze('$header a[b] *= c; }'); |
+ analyze('$header a[b] /= c; }'); |
+ analyze('$header a[b] %= c; }'); |
+ analyze('$header a[b] ~/= c; }'); |
+ analyze('$header a[b] <<= c; }'); |
+ analyze('$header a[b] >>= c; }'); |
+ analyze('$header a[b] &= c; }'); |
+ analyze('$header a[b] |= c; }'); |
+ analyze('$header a[b] ^= c; }'); |
+ |
+ analyze('$header a += b; }'); |
+ analyze('$header a -= b; }'); |
+ analyze('$header a *= b; }'); |
+ analyze('$header a /= b; }'); |
+ analyze('$header a %= b; }'); |
+ analyze('$header a ~/= b; }'); |
+ |
+ analyze('$header a <<= b; }'); |
+ analyze('$header a >>= b; }'); |
+ |
+ analyze('$header a &= b; }'); |
+ analyze('$header a |= b; }'); |
+ analyze('$header a ^= b; }'); |
+ |
+ // Negative tests on operators. |
+ |
+ // `0` is not assignable to operator+ on `a`. |
karlklose
2013/05/17 09:36:53
Actually "The type of `0` is not assignable to the
Johnni Winther
2013/05/17 11:46:33
Added a comment on the terminology.
|
+ analyze('$header c = a + 0; }', MessageKind.NOT_ASSIGNABLE); |
+ // `a + b` is not assignable to `z`. |
+ analyze('$header z = a + b; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ // `-a` is not assignable to `z`. |
+ analyze('$header z = -a; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ // `0` is not assignable to operator[] on `a`. |
+ analyze('$header c = a[0]; }', MessageKind.NOT_ASSIGNABLE); |
+ // `a[b]` is not assignable to `z`. |
+ analyze('$header 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`. |
+ analyze('$header a[0] *= c; }', MessageKind.NOT_ASSIGNABLE); |
+ // `z` is not assignable to operator* on `a[0]`. |
+ analyze('$header a[b] *= z; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ analyze('$header b = a++; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header b = ++a; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header b = a--; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header b = --a; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ analyze('$header c = a[b]++; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header c = ++a[b]; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header c = a[b]--; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header c = --a[b]; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ analyze('$header z = a == b; }'); |
+ analyze('$header z = a != b; }'); |
+ |
+ analyze('$header z = z && z; }'); |
+ analyze('$header z = a && z; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header z = z && b; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header z = a && b; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ analyze('$header a = a && b; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE, |
+ MessageKind.NOT_ASSIGNABLE]); |
+ |
+ analyze('$header z = z || z; }'); |
karlklose
2013/05/17 09:36:53
Perhaps you can combine l.664-671 and l.673-680 in
Johnni Winther
2013/05/17 11:46:33
Done.
|
+ analyze('$header z = a || z; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header z = z || b; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header z = a || b; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ analyze('$header a = a || b; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE, |
+ MessageKind.NOT_ASSIGNABLE]); |
+ |
+ analyze('$header z = !z; }'); |
+ analyze('$header z = !a; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header a = !z; }', MessageKind.NOT_ASSIGNABLE); |
+ analyze('$header a = !a; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ |
+ |
+ // Tests against MismatchA. |
+ |
+ header = """{ |
+ MismatchA a = new MismatchA(); |
+ MismatchA b = new MismatchA(); |
+ MismatchA c; |
+ """; |
+ |
+ // Tests against int operator+(MismatchA other) => 0; |
+ |
+ // `a + b` is not assignable to `c`. |
+ analyze('$header c = a + b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `a + b` is not assignable to `a`. |
+ analyze('$header a += b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `a[0] + b` is not assignable to `a[0]`. |
+ analyze('$header a[0] += b; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ // 1 is not applicable to operator+ |
+ analyze('$header b = a++; }', MessageKind.NOT_ASSIGNABLE); |
+ // 1 is not applicable to operator+. |
+ // `++a` of type int is not assignable to `b`. |
+ analyze('$header b = ++a; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ |
+ // 1 is not applicable to operator+. |
+ analyze('$header b = a[0]++; }', MessageKind.NOT_ASSIGNABLE); |
+ // 1 is not applicable to operator+. |
+ // `++a[0]` of type int is not assignable to `b`. |
+ analyze('$header b = ++a[0]; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ |
+ // Tests against: MismatchA operator-(int other) => this; |
+ |
+ // `a - b` is not assignable to `c`. |
+ analyze('$header c = a + b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `a - b` is not assignable to `a`. |
+ analyze('$header a += b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `a[0] - b` is not assignable to `a[0]`. |
+ analyze('$header a[0] += b; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ analyze('$header b = a--; }'); |
+ analyze('$header b = --a; }'); |
+ |
+ analyze('$header b = a[0]--; }'); |
+ analyze('$header b = --a[0]; }'); |
+ |
+ // Tests against MismatchB. |
+ |
+ header = """{ |
+ MismatchB a = new MismatchB(); |
+ MismatchB b = new MismatchB(); |
+ MismatchB c; |
+ """; |
+ |
+ // Tests against: |
+ // MismatchB operator[](int key) => this; |
+ // void operator[]=(String key, MismatchB value) {} |
+ |
+ // `0` is not applicable to operator[]= on `a`. |
+ analyze('$header a[0] = b; }', MessageKind.NOT_ASSIGNABLE); |
+ |
+ // `0` is not applicable to operator[]= on `a`. |
+ analyze('$header a[0] += b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `""` is not applicable to operator[] on `a`. |
+ analyze('$header a[""] += b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `c` is not applicable to operator[] on `a`. |
+ // `c` is not applicable to operator[]= on `a`. |
+ analyze('$header a[c] += b; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ |
+ |
+ // Tests against MismatchB. |
+ |
+ header = """{ |
+ MismatchC a = new MismatchC(); |
+ MismatchC b = new MismatchC(); |
+ MismatchC c; |
+ """; |
+ |
+ // Tests against: |
+ // MismatchC operator[](int key) => this; |
+ // void operator[]=(int key, String value) {} |
+ |
+ // `b` is not assignable to `a[0]`. |
+ analyze('$header a[0] += b; }', MessageKind.NOT_ASSIGNABLE); |
+ // `0` is not applicable to operator+ on `a[0]`. |
+ analyze('$header a[0] += ""; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+ // `true` is not applicable to operator+ on `a[0]`. |
+ // `true` is not assignable to `a[0]`. |
+ analyze('$header a[0] += true; }', |
+ [MessageKind.NOT_ASSIGNABLE, MessageKind.NOT_ASSIGNABLE]); |
+} |
+ |
const CLASS_WITH_METHODS = ''' |
class ClassWithMethods { |
untypedNoArgumentMethod() {} |
@@ -531,8 +816,45 @@ String returnWithType(String type, 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() { |
- compiler = new MockCompiler(); |
+ 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 |
karlklose
2013/05/17 09:36:53
Should we make sure that classNum and ClassInt are
Johnni Winther
2013/05/17 11:46:33
We do - in the lines above.
|
+ .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); |
@@ -570,23 +892,56 @@ analyzeTopLevel(String text, [expectedWarnings]) { |
} |
} |
+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, compiler.mainApp); |
+ 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; |
karlklose
2013/05/17 09:36:53
Add spaces around +.
Johnni Winther
2013/05/17 11:46:33
Done.
|
+ SourceFile sourceFile = new SourceFile('analysis', text); |
+ print(sourceFile.getLocationMessage(message.message.toString(), |
+ begin, end, true, (str) => str)); |
+ } |
} |
analyzeIn(ClassElement classElement, String text, [expectedWarnings]) { |
@@ -603,7 +958,7 @@ analyzeIn(ClassElement classElement, String text, [expectedWarnings]) { |
TypeCheckerVisitor checker = new TypeCheckerVisitor(compiler, elements, |
types); |
compiler.clearWarnings(); |
- checker.currentClass = classElement; |
checker.analyze(node); |
+ generateOutput(text); |
compareWarningKinds(text, expectedWarnings, compiler.warnings); |
} |