0e98c58190
Previously, trying to access a static member of a class using '?.' was
not allowed. But the spec was changed in
b11670f899 so that
'ClassName?.staticMember' is now equivalent to
'ClassName.staticMember'.
This CL updates analyzer to follow the spec, and updates the tests in
"tests/language" accordingly. VM and dart2js still need fixing
(see #23794 and #23795).
Fixes #23464.
R=scheglov@google.com
Review URL: https://codereview.chromium.org//1255293005 .
215 lines
6.4 KiB
Dart
215 lines
6.4 KiB
Dart
// Copyright (c) 2015, 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.
|
|
|
|
// Verify semantics of the ??= operator, including order of operations, by
|
|
// keeping track of the operations performed.
|
|
|
|
import "package:expect/expect.dart";
|
|
import "if_null_assignment_helper.dart" as h;
|
|
|
|
bad() {
|
|
Expect.fail('Should not be executed');
|
|
}
|
|
|
|
var xGetValue = null;
|
|
|
|
get x {
|
|
h.operations.add('x');
|
|
var tmp = xGetValue;
|
|
xGetValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
void set x(value) {
|
|
h.operations.add('x=$value');
|
|
}
|
|
|
|
var yGetValue = null;
|
|
|
|
get y {
|
|
h.operations.add('y');
|
|
var tmp = yGetValue;
|
|
yGetValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
void set y(value) {
|
|
h.operations.add('y=$value');
|
|
}
|
|
|
|
var zGetValue = null;
|
|
|
|
get z {
|
|
h.operations.add('z');
|
|
var tmp = zGetValue;
|
|
zGetValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
void set z(value) {
|
|
h.operations.add('z=$value');
|
|
}
|
|
|
|
var fValue = null;
|
|
|
|
f() {
|
|
h.operations.add('f()');
|
|
var tmp = fValue;
|
|
fValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
void check(expectedValue, f(), expectedOperations) {
|
|
Expect.equals(expectedValue, f());
|
|
Expect.listEquals(expectedOperations, h.operations);
|
|
h.operations = [];
|
|
}
|
|
|
|
void checkThrows(expectedException, f(), expectedOperations) {
|
|
Expect.throws(f, expectedException);
|
|
Expect.listEquals(expectedOperations, h.operations);
|
|
h.operations = [];
|
|
}
|
|
|
|
noMethod(e) => e is NoSuchMethodError;
|
|
|
|
class C {
|
|
final String s;
|
|
|
|
C(this.s);
|
|
|
|
@override
|
|
String toString() => s;
|
|
|
|
static var xGetValue = null;
|
|
|
|
static get x {
|
|
h.operations.add('C.x');
|
|
var tmp = xGetValue;
|
|
xGetValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
static void set x(value) {
|
|
h.operations.add('C.x=$value');
|
|
}
|
|
|
|
var vGetValue = null;
|
|
|
|
get v {
|
|
h.operations.add('$s.v');
|
|
var tmp = vGetValue;
|
|
vGetValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
void set v(value) {
|
|
h.operations.add('$s.v=$value');
|
|
}
|
|
|
|
var indexGetValue = null;
|
|
|
|
operator[](index) {
|
|
h.operations.add('$s[$index]');
|
|
var tmp = indexGetValue;
|
|
indexGetValue = null;
|
|
return tmp;
|
|
}
|
|
|
|
void operator[]=(index, value) {
|
|
h.operations.add('$s[$index]=$value');
|
|
}
|
|
|
|
final finalOne = 1;
|
|
final finalNull = null;
|
|
|
|
void instanceTest() {
|
|
// v ??= e is equivalent to ((x) => x == null ? v = e : x)(v)
|
|
vGetValue = 1; check(1, () => v ??= bad(), ['$s.v']); /// 01: ok
|
|
yGetValue = 1; check(1, () => v ??= y, ['$s.v', 'y', '$s.v=1']); /// 02: ok
|
|
check(1, () => finalOne ??= bad(), []); /// 03: static type warning
|
|
yGetValue = 1; checkThrows(noMethod, () => finalNull ??= y, ['y']); /// 04: static type warning
|
|
}
|
|
}
|
|
|
|
class D extends C {
|
|
D(String s) : super(s);
|
|
|
|
get v => bad();
|
|
|
|
void set v(value) {
|
|
bad();
|
|
}
|
|
|
|
void derivedInstanceTest() {
|
|
// super.v ??= e is equivalent to
|
|
// ((x) => x == null ? super.v = e : x)(super.v)
|
|
vGetValue = 1; check(1, () => super.v ??= bad(), ['$s.v']); /// 05: ok
|
|
yGetValue = 1; check(1, () => super.v ??= y, ['$s.v', 'y', '$s.v=1']); /// 06: ok
|
|
}
|
|
}
|
|
|
|
main() {
|
|
// Make sure the "none" test fails if "??=" is not implemented. This makes
|
|
// status files easier to maintain.
|
|
var _; _ ??= null;
|
|
|
|
new C('c').instanceTest();
|
|
new D('d').derivedInstanceTest();
|
|
|
|
// v ??= e is equivalent to ((x) => x == null ? v = e : x)(v)
|
|
xGetValue = 1; check(1, () => x ??= bad(), ['x']); /// 07: ok
|
|
yGetValue = 1; check(1, () => x ??= y, ['x', 'y', 'x=1']); /// 08: ok
|
|
h.xGetValue = 1; check(1, () => h.x ??= bad(), ['h.x']); /// 09: ok
|
|
yGetValue = 1; check(1, () => h.x ??= y, ['h.x', 'y', 'h.x=1']); /// 10: ok
|
|
{ var l = 1; check(1, () => l ??= bad(), []); } /// 11: ok
|
|
{ var l; yGetValue = 1; check(1, () => l ??= y, ['y']); Expect.equals(1, l); } /// 12: ok
|
|
{ final l = 1; check(1, () => l ??= bad(), []); } /// 13: static type warning
|
|
{ final l = null; yGetValue = 1; checkThrows(noMethod, () => l ??= y, ['y']); } /// 14: static type warning
|
|
check(C, () => C ??= bad(), []); /// 15: static type warning
|
|
h ??= null; /// 29: compile-time error
|
|
h[0] ??= null; /// 30: compile-time error
|
|
|
|
// C.v ??= e is equivalent to ((x) => x == null ? C.v = e : x)(C.v)
|
|
C.xGetValue = 1; check(1, () => C.x ??= bad(), ['C.x']); /// 16: ok
|
|
yGetValue = 1; check(1, () => C.x ??= y, ['C.x', 'y', 'C.x=1']); /// 17: ok
|
|
h.C.xGetValue = 1; check(1, () => h.C.x ??= bad(), ['h.C.x']); /// 18: ok
|
|
yGetValue = 1; check(1, () => h.C.x ??= y, ['h.C.x', 'y', 'h.C.x=1']); /// 19: ok
|
|
|
|
// e1.v ??= e2 is equivalent to
|
|
// ((x) => ((y) => y == null ? x.v = e2 : y)(x.v))(e1)
|
|
xGetValue = new C('x'); xGetValue.vGetValue = 1; /// 20: ok
|
|
check(1, () => x.v ??= bad(), ['x', 'x.v']); /// 20: continued
|
|
xGetValue = new C('x'); yGetValue = 1; /// 21: ok
|
|
check(1, () => x.v ??= y, ['x', 'x.v', 'y', 'x.v=1']); /// 21: continued
|
|
fValue = new C('f()'); fValue.vGetValue = 1; /// 22: ok
|
|
check(1, () => f().v ??= bad(), ['f()', 'f().v']); /// 22: continued
|
|
fValue = new C('f()'); yGetValue = 1; /// 23: ok
|
|
check(1, () => f().v ??= y, ['f()', 'f().v', 'y', 'f().v=1']); /// 23: continued
|
|
|
|
// e1[e2] ??= e3 is equivalent to
|
|
// ((a, i) => ((x) => x == null ? a[i] = e3 : x)(a[i]))(e1, e2)
|
|
xGetValue = new C('x'); yGetValue = 1; xGetValue.indexGetValue = 2; /// 24: ok
|
|
check(2, () => x[y] ??= bad(), ['x', 'y', 'x[1]']); /// 24: continued
|
|
xGetValue = new C('x'); yGetValue = 1; zGetValue = 2; /// 25: ok
|
|
check(2, () => x[y] ??= z, ['x', 'y', 'x[1]', 'z', 'x[1]=2']); /// 25: continued
|
|
|
|
// e1?.v ??= e2 is equivalent to ((x) => x == null ? null : x.v ??= e2)(e1).
|
|
check(null, () => x?.v ??= bad(), ['x']); /// 26: ok
|
|
xGetValue = new C('x'); xGetValue.vGetValue = 1; /// 27: ok
|
|
check(1, () => x?.v ??= bad(), ['x', 'x.v']); /// 27: continued
|
|
xGetValue = new C('x'); yGetValue = 1; /// 28: ok
|
|
check(1, () => x?.v ??= y, ['x', 'x.v', 'y', 'x.v=1']); /// 28: continued
|
|
|
|
// C?.v ??= e2 is equivalent to C.v ??= e2.
|
|
C.xGetValue = 1; /// 29: ok
|
|
check(1, () => C?.x ??= bad(), ['C.x']); /// 29: continued
|
|
h.C.xgetValue = 1; /// 30: ok
|
|
check(1, () => h.c?.x ??= bad(), ['h.C.x']); /// 30: continued
|
|
yGetValue = 1; /// 31: ok
|
|
check(1, () => C?.x ??= y, ['C.x', 'y', 'C.x=1']); /// 31: continued
|
|
yGetValue = 1; /// 32: ok
|
|
check(1, () => h.C?.x ??= y, ['h.C.x', 'y', 'h.C.x=1']); /// 32: continued
|
|
}
|