Skip to content

Commit 0e48841

Browse files
committed
Implement the new '??=' operator in analyzer.
As in r44802, the new operator is only accepted by the tokenizer if the command-line option "--enable-null-aware-operators" is supplied. Only static analysis of '??=' is implemented; however, the tests in tests/language will verify behavior once it is implemented. [email protected] Review URL: https://codereview.chromium.org//1052243002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@44873 260f80e4-7a28-3924-810f-c04153c831b5
1 parent f6028da commit 0e48841

12 files changed

+476
-2
lines changed

pkg/analyzer/lib/src/generated/element_resolver.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ class ElementResolver extends SimpleAstVisitor<Object> {
146146
Object visitAssignmentExpression(AssignmentExpression node) {
147147
sc.Token operator = node.operator;
148148
sc.TokenType operatorType = operator.type;
149-
if (operatorType != sc.TokenType.EQ) {
149+
if (operatorType != sc.TokenType.EQ &&
150+
operatorType != sc.TokenType.QUESTION_QUESTION_EQ) {
150151
operatorType = _operatorFromCompoundAssignment(operatorType);
151152
Expression leftHandSide = node.leftHandSide;
152153
if (leftHandSide != null) {

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
309309
sc.TokenType operatorType = node.operator.type;
310310
Expression lhs = node.leftHandSide;
311311
Expression rhs = node.rightHandSide;
312-
if (operatorType == sc.TokenType.EQ) {
312+
if (operatorType == sc.TokenType.EQ ||
313+
operatorType == sc.TokenType.QUESTION_QUESTION_EQ) {
313314
_checkForInvalidAssignment(lhs, rhs);
314315
} else {
315316
_checkForInvalidCompoundAssignment(node, lhs, rhs);

pkg/analyzer/lib/src/generated/static_type_analyzer.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<Object> {
157157
overrideType = propagatedType;
158158
}
159159
_resolver.overrideExpression(node.leftHandSide, overrideType, true);
160+
} else if (operator == sc.TokenType.QUESTION_QUESTION_EQ) {
161+
// The static type of a compound assignment using ??= is the least upper
162+
// bound of the static types of the LHS and RHS.
163+
_analyzeLeastUpperBound(node, node.leftHandSide, node.rightHandSide);
164+
return null;
160165
} else {
161166
ExecutableElement staticMethodElement = node.staticElement;
162167
DartType staticType = _computeStaticReturnType(staticMethodElement);

pkg/analyzer/test/generated/non_error_resolver_test.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ library engine.non_error_resolver_test;
66

77
import 'package:analyzer/src/generated/ast.dart';
88
import 'package:analyzer/src/generated/element.dart';
9+
import 'package:analyzer/src/generated/engine.dart';
910
import 'package:analyzer/src/generated/error.dart';
1011
import 'package:analyzer/src/generated/parser.dart' show ParserErrorCode;
1112
import 'package:analyzer/src/generated/source_io.dart';
@@ -2307,6 +2308,36 @@ f([String x = '0']) {
23072308
verify([source]);
23082309
}
23092310

2311+
void test_invalidAssignment_ifNullAssignment_compatibleType() {
2312+
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
2313+
options.enableNullAwareOperators = true;
2314+
resetWithOptions(options);
2315+
Source source = addSource('''
2316+
void f(int i) {
2317+
num n;
2318+
n ??= i;
2319+
}
2320+
''');
2321+
resolve(source);
2322+
assertNoErrors(source);
2323+
verify([source]);
2324+
}
2325+
2326+
void test_invalidAssignment_ifNullAssignment_sameType() {
2327+
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
2328+
options.enableNullAwareOperators = true;
2329+
resetWithOptions(options);
2330+
Source source = addSource('''
2331+
void f(int i) {
2332+
int j;
2333+
j ??= i;
2334+
}
2335+
''');
2336+
resolve(source);
2337+
assertNoErrors(source);
2338+
verify([source]);
2339+
}
2340+
23102341
void test_invalidAssignment_implicitlyImplementFunctionViaCall_1() {
23112342
// 18341
23122343
//

pkg/analyzer/test/generated/resolver_test.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9892,6 +9892,24 @@ class StaticTypeAnalyzerTest extends EngineTestCase {
98929892
_listener.assertNoErrors();
98939893
}
98949894

9895+
void test_visitAssignmentExpression_compoundIfNull_differentTypes() {
9896+
// double d; d ??= 0
9897+
Expression node = AstFactory.assignmentExpression(
9898+
_resolvedVariable(_typeProvider.doubleType, 'd'),
9899+
TokenType.QUESTION_QUESTION_EQ, _resolvedInteger(0));
9900+
expect(_analyze(node), same(_typeProvider.numType));
9901+
_listener.assertNoErrors();
9902+
}
9903+
9904+
void test_visitAssignmentExpression_compoundIfNull_sameTypes() {
9905+
// int i; i ??= 0
9906+
Expression node = AstFactory.assignmentExpression(
9907+
_resolvedVariable(_typeProvider.intType, 'i'),
9908+
TokenType.QUESTION_QUESTION_EQ, _resolvedInteger(0));
9909+
expect(_analyze(node), same(_typeProvider.intType));
9910+
_listener.assertNoErrors();
9911+
}
9912+
98959913
void test_visitAssignmentExpression_simple() {
98969914
// i = 0
98979915
InterfaceType intType = _typeProvider.intType;

pkg/analyzer/test/generated/static_type_warning_code_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,21 @@ main() {
355355
verify([source]);
356356
}
357357

358+
void test_invalidAssignment_ifNullAssignment() {
359+
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
360+
options.enableNullAwareOperators = true;
361+
resetWithOptions(options);
362+
Source source = addSource('''
363+
void f(int i) {
364+
double d;
365+
d ??= i;
366+
}
367+
''');
368+
resolve(source);
369+
assertErrors(source, [StaticTypeWarningCode.INVALID_ASSIGNMENT]);
370+
verify([source]);
371+
}
372+
358373
void test_invalidAssignment_instanceVariable() {
359374
Source source = addSource(r'''
360375
class A {
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Verify semantics of the ??= operator, including order of operations, by
6+
// keeping track of the operations performed.
7+
8+
// SharedOptions=--enable-null-aware-operators
9+
10+
import "package:expect/expect.dart";
11+
import "if_null_assignment_helper.dart" as h;
12+
13+
bad() {
14+
Expect.fail('Should not be executed');
15+
}
16+
17+
var xGetValue = null;
18+
19+
get x {
20+
h.operations.add('x');
21+
var tmp = xGetValue;
22+
xGetValue = null;
23+
return tmp;
24+
}
25+
26+
void set x(value) {
27+
h.operations.add('x=$value');
28+
}
29+
30+
var yGetValue = null;
31+
32+
get y {
33+
h.operations.add('y');
34+
var tmp = yGetValue;
35+
yGetValue = null;
36+
return tmp;
37+
}
38+
39+
void set y(value) {
40+
h.operations.add('y=$value');
41+
}
42+
43+
var zGetValue = null;
44+
45+
get z {
46+
h.operations.add('z');
47+
var tmp = zGetValue;
48+
zGetValue = null;
49+
return tmp;
50+
}
51+
52+
void set z(value) {
53+
h.operations.add('z=$value');
54+
}
55+
56+
var fValue = null;
57+
58+
f() {
59+
h.operations.add('f()');
60+
var tmp = fValue;
61+
fValue = null;
62+
return tmp;
63+
}
64+
65+
void check(expectedValue, f(), expectedOperations) {
66+
Expect.equals(expectedValue, f());
67+
Expect.listEquals(expectedOperations, h.operations);
68+
h.operations = [];
69+
}
70+
71+
void checkThrows(expectedException, f(), expectedOperations) {
72+
Expect.throws(f, expectedException);
73+
Expect.listEquals(expectedOperations, h.operations);
74+
h.operations = [];
75+
}
76+
77+
noMethod(e) => e is NoSuchMethodError;
78+
79+
class C {
80+
final String s;
81+
82+
C(this.s);
83+
84+
@override
85+
String toString() => s;
86+
87+
static var xGetValue = null;
88+
89+
static get x {
90+
h.operations.add('C.x');
91+
var tmp = xGetValue;
92+
xGetValue = null;
93+
return tmp;
94+
}
95+
96+
static void set x(value) {
97+
h.operations.add('C.x=$value');
98+
}
99+
100+
var vGetValue = null;
101+
102+
get v {
103+
h.operations.add('$s.v');
104+
var tmp = vGetValue;
105+
vGetValue = null;
106+
return tmp;
107+
}
108+
109+
void set v(value) {
110+
h.operations.add('$s.v=$value');
111+
}
112+
113+
var indexGetValue = null;
114+
115+
operator[](index) {
116+
h.operations.add('$s[$index]');
117+
var tmp = indexGetValue;
118+
indexGetValue = null;
119+
return tmp;
120+
}
121+
122+
void operator[]=(index, value) {
123+
h.operations.add('$s[$index]=$value');
124+
}
125+
126+
final finalOne = 1;
127+
final finalNull = null;
128+
129+
void instanceTest() {
130+
// v ??= e is equivalent to ((x) => x == null ? v = e : x)(v)
131+
vGetValue = 1; check(1, () => v ??= bad(), ['$s.v']); /// 01: ok
132+
yGetValue = 1; check(1, () => v ??= y, ['$s.v', 'y', '$s.v=1']); /// 02: ok
133+
check(1, () => finalOne ??= bad(), []); /// 03: static type warning
134+
yGetValue = 1; checkThrows(noMethod, () => finalNull ??= y, ['y']); /// 04: static type warning
135+
}
136+
}
137+
138+
class D extends C {
139+
D(String s) : super(s);
140+
141+
get v => bad();
142+
143+
void set v(value) {
144+
bad();
145+
}
146+
147+
void derivedInstanceTest() {
148+
// super.v ??= e is equivalent to
149+
// ((x) => x == null ? super.v = e : x)(super.v)
150+
vGetValue = 1; check(1, () => super.v ??= bad(), ['$s.v']); /// 05: ok
151+
yGetValue = 1; check(1, () => super.v ??= y, ['$s.v', 'y', '$s.v=1']); /// 06: ok
152+
}
153+
}
154+
155+
main() {
156+
// Make sure the "none" test fails if "??=" is not implemented. This makes
157+
// status files easier to maintain.
158+
var _; _ ??= null;
159+
160+
new C('c').instanceTest();
161+
new D('d').derivedInstanceTest();
162+
163+
// v ??= e is equivalent to ((x) => x == null ? v = e : x)(v)
164+
xGetValue = 1; check(1, () => x ??= bad(), ['x']); /// 07: ok
165+
yGetValue = 1; check(1, () => x ??= y, ['x', 'y', 'x=1']); /// 08: ok
166+
h.xGetValue = 1; check(1, () => h.x ??= bad(), ['h.x']); /// 09: ok
167+
yGetValue = 1; check(1, () => h.x ??= y, ['h.x', 'y', 'h.x=1']); /// 10: ok
168+
{ var l = 1; check(1, () => l ??= bad(), []); } /// 11: ok
169+
{ var l; yGetValue = 1; check(1, () => l ??= y, ['y']); Expect.equals(1, l); } /// 12: ok
170+
{ final l = 1; check(1, () => l ??= bad(), []); } /// 13: static type warning
171+
{ final l = null; yGetValue = 1; checkThrows(noMethod, () => l ??= y, ['y']); } /// 14: static type warning
172+
check(C, () => C ??= bad(), []); /// 15: static type warning
173+
174+
// C.v ??= e is equivalent to ((x) => x == null ? C.v = e : x)(C.v)
175+
C.xGetValue = 1; check(1, () => C.x ??= bad(), ['C.x']); /// 16: ok
176+
yGetValue = 1; check(1, () => C.x ??= y, ['C.x', 'y', 'C.x=1']); /// 17: ok
177+
h.C.xGetValue = 1; check(1, () => h.C.x ??= bad(), ['h.C.x']); /// 18: ok
178+
yGetValue = 1; check(1, () => h.C.x ??= y, ['h.C.x', 'y', 'h.C.x=1']); /// 19: ok
179+
180+
// e1.v ??= e2 is equivalent to
181+
// ((x) => ((y) => y == null ? x.v = e2 : y)(x.v))(e1)
182+
xGetValue = new C('x'); xGetValue.vGetValue = 1; /// 20: ok
183+
check(1, () => x.v ??= bad(), ['x', 'x.v']); /// 20: continued
184+
xGetValue = new C('x'); yGetValue = 1; /// 21: ok
185+
check(1, () => x.v ??= y, ['x', 'x.v', 'y', 'x.v=1']); /// 21: continued
186+
fValue = new C('f()'); fValue.vGetValue = 1; /// 22: ok
187+
check(1, () => f().v ??= bad(), ['f()', 'f().v']); /// 22: continued
188+
fValue = new C('f()'); yGetValue = 1; /// 23: ok
189+
check(1, () => f().v ??= y, ['f()', 'f().v', 'y', 'f().v=1']); /// 23: continued
190+
191+
// e1[e2] ??= e3 is equivalent to
192+
// ((a, i) => ((x) => x == null ? a[i] = e3 : x)(a[i]))(e1, e2)
193+
xGetValue = new C('x'); yGetValue = 1; xGetValue.indexGetValue = 2; /// 24: ok
194+
check(2, () => x[y] ??= bad(), ['x', 'y', 'x[1]']); /// 24: continued
195+
xGetValue = new C('x'); yGetValue = 1; zGetValue = 2; /// 25: ok
196+
check(2, () => x[y] ??= z, ['x', 'y', 'x[1]', 'z', 'x[1]=2']); /// 25: continued
197+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Library used by if_null_assignment_behavior_test.dart, which
6+
// imports it using the prefix "h.".
7+
8+
library lib;
9+
10+
import "package:expect/expect.dart";
11+
12+
List<String> operations = [];
13+
14+
var xGetValue = null;
15+
16+
get x {
17+
operations.add('h.x');
18+
var tmp = xGetValue;
19+
xGetValue = null;
20+
return tmp;
21+
}
22+
23+
void set x(value) {
24+
operations.add('h.x=$value');
25+
}
26+
27+
class C {
28+
static var xGetValue = null;
29+
30+
static get x {
31+
operations.add('h.C.x');
32+
var tmp = xGetValue;
33+
xGetValue = null;
34+
return tmp;
35+
}
36+
37+
static void set x(value) {
38+
operations.add('h.C.x=$value');
39+
}
40+
}

0 commit comments

Comments
 (0)