Fix operator handling.

Review URL: https://codereview.chromium.org//11348067

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15524 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
polux@google.com 2012-11-29 16:07:02 +00:00
parent e08d148977
commit 985223c91a
3 changed files with 156 additions and 70 deletions

View file

@ -1949,7 +1949,8 @@ class Elements {
}
}
static SourceString constructOperatorName(SourceString op, bool isUnary) {
static SourceString constructOperatorNameOrNull(SourceString op,
bool isUnary) {
String value = op.stringValue;
if ((identical(value, '==')) ||
(identical(value, '~')) ||
@ -1974,11 +1975,17 @@ class Elements {
} else if (identical(value, '-')) {
return isUnary ? const SourceString('unary-') : op;
} else {
throw 'Unhandled operator: ${op.slowToString()}';
return null;
}
}
static SourceString mapToUserOperator(SourceString op) {
static SourceString constructOperatorName(SourceString op, bool isUnary) {
SourceString operatorName = constructOperatorNameOrNull(op, isUnary);
if (operatorName == null) throw 'Unhandled operator: ${op.slowToString()}';
else return operatorName;
}
static SourceString mapToUserOperatorOrNull(SourceString op) {
String value = op.stringValue;
if (identical(value, '!=')) return const SourceString('==');
@ -1994,7 +2001,13 @@ class Elements {
if (identical(value, '^=')) return const SourceString('^');
if (identical(value, '|=')) return const SourceString('|');
throw 'Unhandled operator: ${op.slowToString()}';
return null;
}
static SourceString mapToUserOperator(SourceString op) {
SourceString userOperator = mapToUserOperatorOrNull(op);
if (userOperator == null) throw 'Unhandled operator: ${op.slowToString()}';
else return userOperator;
}
static bool isNumberOrStringSupertype(Element element, Compiler compiler) {

View file

@ -94,6 +94,7 @@ abstract class ConcreteType {
ConcreteType union(ConcreteType other);
bool isUnkown();
bool isEmpty();
Set<BaseType> get baseTypes;
/**
@ -109,6 +110,7 @@ abstract class ConcreteType {
class UnknownConcreteType implements ConcreteType {
const UnknownConcreteType();
bool isUnkown() => true;
bool isEmpty() => false;
bool operator ==(ConcreteType other) => identical(this, other);
Set<BaseType> get baseTypes =>
new Set<BaseType>.from([const UnknownBaseType()]);
@ -131,6 +133,7 @@ class UnionType implements ConcreteType {
UnionType(this.baseTypes);
bool isUnkown() => false;
bool isEmpty() => baseTypes.isEmpty;
bool operator ==(ConcreteType other) {
if (other is! UnionType) return false;
@ -1055,31 +1058,54 @@ class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
return argumentType;
}
SourceString canonicalizeCompoundOperator(String op) {
SourceString canonicalizeCompoundOperator(SourceString op) {
// TODO(ahe): This class should work on elements or selectors, not
// names. Otherwise, it is repeating work the resolver has
// already done (or should have done). In this case, the problem
// is that the resolver is not recording the selectors it is
// registering in registerBinaryOperator in
// ResolverVisitor.visitSendSet.
if (op == '++') return const SourceString(r'+');
else return const SourceString(r'-');
String stringValue = op.stringValue;
if (stringValue == '++') return const SourceString(r'+');
else if (stringValue == '--') return const SourceString(r'-');
else return Elements.mapToUserOperatorOrNull(op);
}
// TODO(polux): handle sendset as expression
ConcreteType visitSendSet(SendSet node) {
Identifier selector = node.selector;
final name = node.assignmentOperator.source.stringValue;
// Operator []= has a different behaviour than other send sets: it is
// actually a send whose return type is that of its second argument.
if (node.selector.asIdentifier().source.stringValue == '[]') {
ConcreteType receiverType = analyze(node.receiver);
ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments);
analyzeDynamicSend(receiverType, const SourceString('[]='),
argumentsTypes);
return argumentsTypes.positional[1];
}
// All other operators have a single argument (++ and -- have an implicit
// argument: 1). We will store its type in argumentType.
ConcreteType argumentType;
if (name == '++' || name == '--') {
SourceString operatorName = node.assignmentOperator.source;
SourceString compoundOperatorName =
canonicalizeCompoundOperator(node.assignmentOperator.source);
// ++, --, +=, -=, ...
if (compoundOperatorName != null) {
ConcreteType receiverType = visitGetterSend(node);
SourceString canonicalizedMethodName = canonicalizeCompoundOperator(name);
List<ConcreteType> positionalArguments = <ConcreteType>[
new ConcreteType.singleton(inferrer.baseTypes.intBaseType)];
ArgumentsTypes argumentsTypes =
new ArgumentsTypes(positionalArguments, new Map());
argumentType = analyzeDynamicSend(receiverType, canonicalizedMethodName,
// argumentsTypes is either computed from the actual arguments or [{int}]
// in case of ++ or --.
ArgumentsTypes argumentsTypes;
if (operatorName.stringValue == '++'
|| operatorName.stringValue == '--') {
List<ConcreteType> positionalArguments = <ConcreteType>[
new ConcreteType.singleton(inferrer.baseTypes.intBaseType)];
argumentsTypes = new ArgumentsTypes(positionalArguments, new Map());
} else {
argumentsTypes = analyzeArguments(node.arguments);
}
argumentType = analyzeDynamicSend(receiverType, compoundOperatorName,
argumentsTypes);
// The simple assignment case: receiver = argument.
} else {
argumentType = analyze(node.argumentsNode);
}
@ -1370,47 +1396,6 @@ class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
inferrer.fail(node, 'not implemented');
}
// TODO(polux): handle unary operators and share this list with the rest of
// dart2js.
final Set<SourceString> operators = new Set<SourceString>()
..add(const SourceString('=='))
..add(const SourceString('!='))
..add(const SourceString('~'))
..add(const SourceString('[]'))
..add(const SourceString('[]='))
..add(const SourceString('*'))
..add(const SourceString('*='))
..add(const SourceString('/'))
..add(const SourceString('/='))
..add(const SourceString('%'))
..add(const SourceString('%='))
..add(const SourceString('~/'))
..add(const SourceString('~/='))
..add(const SourceString('+'))
..add(const SourceString('+='))
..add(const SourceString('-'))
..add(const SourceString('-='))
..add(const SourceString('<<'))
..add(const SourceString('<<='))
..add(const SourceString('>>'))
..add(const SourceString('>>='))
..add(const SourceString('>='))
..add(const SourceString('>'))
..add(const SourceString('<='))
..add(const SourceString('<'))
..add(const SourceString('&'))
..add(const SourceString('&='))
..add(const SourceString('^'))
..add(const SourceString('^='))
..add(const SourceString('|'))
..add(const SourceString('|='));
SourceString canonicalizeMethodName(SourceString s) {
return operators.contains(s)
? Elements.constructOperatorName(s, false)
: s;
}
ConcreteType analyzeDynamicSend(ConcreteType receiverType,
SourceString canonicalizedMethodName,
ArgumentsTypes argumentsTypes) {
@ -1448,6 +1433,14 @@ class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
return result;
}
SourceString canonicalizeMethodName(SourceString name) {
// TODO(polux): handle unary-
SourceString operatorName =
Elements.constructOperatorNameOrNull(name, false);
if (operatorName != null) return operatorName;
return name;
}
ConcreteType visitDynamicSend(Send node) {
ConcreteType receiverType = (node.receiver != null)
? analyze(node.receiver)
@ -1456,7 +1449,16 @@ class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
SourceString name =
canonicalizeMethodName(node.selector.asIdentifier().source);
ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments);
return analyzeDynamicSend(receiverType, name, argumentsTypes);
if (name.stringValue == '!=') {
ConcreteType returnType = analyzeDynamicSend(receiverType,
const SourceString('=='),
argumentsTypes);
return returnType.isEmpty()
? returnType
: new ConcreteType.singleton(inferrer.baseTypes.boolBaseType);
} else {
return analyzeDynamicSend(receiverType, name, argumentsTypes);
}
}
ConcreteType visitForeignSend(Send node) {

View file

@ -142,7 +142,12 @@ class AnalysisResult {
const String CORELIB = r'''
print(var obj) {}
abstract class num { operator +(x); operator *(x); operator -(x); }
abstract class num {
operator +(x);
operator *(x);
operator -(x);
operator ==(x);
}
abstract class int extends num { }
abstract class double extends num { }
class bool {}
@ -637,25 +642,55 @@ testOperators() {
result.checkNodeHasType('y', [result.string]);
}
testSetIndexOperator() {
final String source = r"""
class A {
var witness1;
var witness2;
operator []=(i, x) { witness1 = i; witness2 = x; }
}
main() {
var x = new A()[42] = "abc";
x;
}
""";
AnalysisResult result = analyze(source);
result.checkNodeHasType('x', [result.string]);
// TODO(polux): the two following results should be [:[null, string:], see
// testFieldInitialization().
result.checkFieldHasType('A', 'witness1', [result.int]);
result.checkFieldHasType('A', 'witness2', [result.string]);
}
testCompoundOperators1() {
final String source = r"""
class A {
operator +(x) => "foo";
}
main() {
var x1 = 1; x1++;
var x2 = 1; ++x2;
var x3 = new A(); x3++;
var x4 = new A(); ++x4;
var x1 = 1;
x1++;
var x2 = 1;
++x2;
var x3 = 1;
x3 += 42;
var x4 = new A();
x4++;
var x5 = new A();
++x5;
var x6 = new A();
x6 += true;
x1; x2; x3; x4;
x1; x2; x3; x4; x5; x6;
}
""";
AnalysisResult result = analyze(source);
result.checkNodeHasType('x1', [result.int]);
result.checkNodeHasType('x2', [result.int]);
result.checkNodeHasType('x3', [result.string]);
result.checkNodeHasType('x3', [result.int]);
result.checkNodeHasType('x4', [result.string]);
result.checkNodeHasType('x5', [result.string]);
result.checkNodeHasType('x6', [result.string]);
}
@ -663,24 +698,58 @@ testCompoundOperators2() {
final String source = r"""
class A {
var xx;
var yy;
var witness1;
var witness2;
var witness3;
var witness4;
A(this.xx);
A(this.xx, this.yy);
get x { witness1 = "foo"; return xx; }
set x(y) { witness2 = "foo"; xx = y; }
set x(a) { witness2 = "foo"; xx = a; }
get y { witness3 = "foo"; return yy; }
set y(a) { witness4 = "foo"; yy = a; }
}
main () {
var a = new A(1);
var a = new A(1, 1);
a.x++;
a.y++;
}
""";
AnalysisResult result = analyze(source);
result.checkFieldHasType('A', 'xx', [result.int]);
// TODO(polux): the two following results should be {null, string}, see
// fieldInitialization().
result.checkFieldHasType('A', 'yy', [result.int]);
// TODO(polux): the four following results should be [:[null, string]:], see
// testFieldInitialization().
result.checkFieldHasType('A', 'witness1', [result.string]);
result.checkFieldHasType('A', 'witness2', [result.string]);
result.checkFieldHasType('A', 'witness3', [result.string]);
result.checkFieldHasType('A', 'witness4', [result.string]);
}
testInequality() {
final String source = r"""
class A {
var witness;
operator ==(x) { witness = "foo"; return "abc"; }
}
class B {
operator ==(x) { throw "error"; }
}
main() {
var foo = 1 != 2;
var bar = new A() != 2;
var baz = new B() != 2;
foo; bar; baz;
}
""";
AnalysisResult result = analyze(source);
result.checkNodeHasType('foo', [result.bool]);
result.checkNodeHasType('bar', [result.bool]);
result.checkNodeHasType('baz', []);
// TODO(polux): the following result should be [:[null, string]:], see
// fieldInitialization().
result.checkFieldHasType('A', 'witness', [result.string]);
}
testFieldInitialization() {
@ -762,6 +831,8 @@ void main() {
testOperators();
testCompoundOperators1();
testCompoundOperators2();
testSetIndexOperator();
testInequality();
// testFieldInitialization(); // TODO(polux)
testSendWithWrongArity();
testDynamicIsAbsorbing();