Skip to content

Commit

Permalink
Promote to NonNull of a tested type on assignment.
Browse files Browse the repository at this point in the history
Bug: dart-lang/sdk#39562
Change-Id: Id55224199f64a8f01b14937ddda6963aff24140c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/143661
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
  • Loading branch information
scheglov authored and commit-bot@chromium.org committed Apr 18, 2020
1 parent 7ee0e10 commit 4814f00
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 90 deletions.
65 changes: 49 additions & 16 deletions pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
Expand Up @@ -1793,27 +1793,60 @@ class VariableModel<Type> {
// supertype of writtenType and a proper subtype of the currently-promoted
// type). If at any point we find an exact match, we take it immediately.
Type currentlyPromotedType = promotedTypes?.last;

List<Type> result;
List<Type> candidates = null;

void handleTypeOfInterest(Type type) {
// The written type must be a subtype of the type.
if (!typeOperations.isSubtypeOf(writtenType, type)) {
return;
}

// Must be more specific that the currently promoted type.
if (currentlyPromotedType != null) {
if (typeOperations.isSameType(type, currentlyPromotedType)) {
return;
}
if (!typeOperations.isSubtypeOf(type, currentlyPromotedType)) {
return;
}
}

// This is precisely the type we want to promote to; take it.
if (typeOperations.isSameType(type, writtenType)) {
result = _addToPromotedTypes(promotedTypes, writtenType);
}

if (candidates == null) {
candidates = [type];
return;
}

// Add only unique candidates.
if (!_typeListContains(typeOperations, candidates, type)) {
candidates.add(type);
return;
}
}

for (int i = 0; i < tested.length; i++) {
Type type = tested[i];
if (!typeOperations.isSubtypeOf(writtenType, type)) {
// Can't promote to this type; the type written is not a subtype of
// it.
} else if (currentlyPromotedType != null &&
!typeOperations.isSubtypeOf(type, currentlyPromotedType)) {
// Can't promote to this type; it's less specific than the currently
// promoted type.
} else if (currentlyPromotedType != null &&
typeOperations.isSameType(type, currentlyPromotedType)) {
// Can't promote to this type; it's the same as the currently
// promoted type.
} else if (typeOperations.isSameType(type, writtenType)) {
// This is precisely the type we want to promote to; take it.
return _addToPromotedTypes(promotedTypes, writtenType);
} else {
(candidates ??= []).add(type);

handleTypeOfInterest(type);
if (result != null) {
return result;
}

var typeNonNull = typeOperations.promoteToNonNull(type);
if (!typeOperations.isSameType(typeNonNull, type)) {
handleTypeOfInterest(typeNonNull);
if (result != null) {
return result;
}
}
}

if (candidates != null) {
// Figure out if we have a unique promotion candidate that's a subtype
// of all the others.
Expand Down
267 changes: 193 additions & 74 deletions pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
Expand Up @@ -1855,7 +1855,7 @@ main() {
expect(s2.reachable, true);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['num?'],
chain: ['num?', 'num'],
ofInterest: ['num?', 'int'],
assigned: true,
unassigned: false)
Expand Down Expand Up @@ -1930,6 +1930,50 @@ main() {
expect(s2.variableInfo, same(s1.variableInfo));
});

group('Promotes to NonNull of a type of interest', () {
test('when promoted', () {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('int?'))
.ifTrue;
expect(s1.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['int?'],
ofInterest: ['int?'],
),
});
var s2 = s1.write(objectQVar, _Type('int'), h);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['int?', 'int'],
ofInterest: ['int?'],
),
});
});

test('when not promoted', () {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('int?'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar: _matchVariableModel(
chain: null,
ofInterest: ['int?'],
),
});
var s2 = s1.write(objectQVar, _Type('int'), h);
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['int'],
ofInterest: ['int?'],
),
});
});
});

test('Promotes to type of interest when not previously promoted', () {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
Expand Down Expand Up @@ -1964,84 +2008,156 @@ main() {
});
});

test('Multiple candidate types of interest; choose most specific (first)',
() {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('int?'))
.ifFalse
.tryPromote(h, objectQVar, _Type('num?'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar:
_matchVariableModel(chain: null, ofInterest: ['num?', 'int?'])
});
var s2 = s1.write(objectQVar, _Type('int'), h);
expect(s2.variableInfo, {
objectQVar:
_matchVariableModel(chain: ['int?'], ofInterest: ['num?', 'int?'])
});
});
group('Multiple candidate types of interest', () {
group('; choose most specific', () {
_Harness h;

setUp(() {
h = _Harness();

// class A {}
// class B extends A {}
// class C extends B {}
h.addSubtype(_Type('A'), _Type('Object?'), true);
h.addSubtype(_Type('A'), _Type('A?'), true);
h.addSubtype(_Type('A'), _Type('B?'), false);
h.addSubtype(_Type('A?'), _Type('Object?'), true);
h.addSubtype(_Type('A?'), _Type('A'), false);
h.addSubtype(_Type('A?'), _Type('B?'), false);
h.addSubtype(_Type('B'), _Type('A'), true);
h.addSubtype(_Type('B'), _Type('A?'), true);
h.addSubtype(_Type('B'), _Type('B?'), true);
h.addSubtype(_Type('B?'), _Type('Object?'), true);
h.addSubtype(_Type('B?'), _Type('A'), false);
h.addSubtype(_Type('B?'), _Type('A?'), true);
h.addSubtype(_Type('B?'), _Type('B'), false);
h.addSubtype(_Type('C'), _Type('A'), true);
h.addSubtype(_Type('C'), _Type('A?'), true);
h.addSubtype(_Type('C'), _Type('B'), true);
h.addSubtype(_Type('C'), _Type('B?'), true);
});

test(
'Multiple candidate types of interest; choose most specific (second)',
() {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('num?'))
.ifFalse
.tryPromote(h, objectQVar, _Type('int?'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar:
_matchVariableModel(chain: null, ofInterest: ['num?', 'int?'])
});
var s2 = s1.write(objectQVar, _Type('int'), h);
expect(s2.variableInfo, {
objectQVar:
_matchVariableModel(chain: ['int?'], ofInterest: ['num?', 'int?'])
});
});
test('; first', () {
var x = _Var('x', _Type('Object?'));

var s1 = FlowModel<_Var, _Type>(true)
.declare(x, true)
.tryPromote(h, x, _Type('B?'))
.ifFalse
.tryPromote(h, x, _Type('A?'))
.ifFalse;
expect(s1.variableInfo, {
x: _matchVariableModel(
chain: null,
ofInterest: ['A?', 'B?'],
),
});

var s2 = s1.write(x, _Type('C'), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['B'],
ofInterest: ['A?', 'B?'],
),
});
});

test('Multiple candidate types of interest; ambiguous', () {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('num?'))
.ifFalse
.tryPromote(h, objectQVar, _Type('num*'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar:
_matchVariableModel(chain: null, ofInterest: ['num?', 'num*'])
test('; second', () {
var x = _Var('x', _Type('Object?'));

var s1 = FlowModel<_Var, _Type>(true)
.declare(x, true)
.tryPromote(h, x, _Type('A?'))
.ifFalse
.tryPromote(h, x, _Type('B?'))
.ifFalse;
expect(s1.variableInfo, {
x: _matchVariableModel(
chain: null,
ofInterest: ['A?', 'B?'],
),
});

var s2 = s1.write(x, _Type('C'), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['B'],
ofInterest: ['A?', 'B?'],
),
});
});

test('; nullable and non-nullable', () {
var x = _Var('x', _Type('Object?'));

var s1 = FlowModel<_Var, _Type>(true)
.declare(x, true)
.tryPromote(h, x, _Type('A'))
.ifFalse
.tryPromote(h, x, _Type('A?'))
.ifFalse;
expect(s1.variableInfo, {
x: _matchVariableModel(
chain: null,
ofInterest: ['A', 'A?'],
),
});

var s2 = s1.write(x, _Type('B'), h);
expect(s2.variableInfo, {
x: _matchVariableModel(
chain: ['A'],
ofInterest: ['A', 'A?'],
),
});
});
});
var s2 = s1.write(objectQVar, _Type('int'), h);
// It's ambiguous whether to promote to num? or num*, so we don't
// promote.
expect(s2, same(s1));
});

test('Multiple candidate types of interest; ambiguous but exact match',
() {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('num?'))
.ifFalse
.tryPromote(h, objectQVar, _Type('num*'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar:
_matchVariableModel(chain: null, ofInterest: ['num?', 'num*'])
group('; ambiguous', () {
test('; no promotion', () {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('num?'))
.ifFalse
.tryPromote(h, objectQVar, _Type('num*'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar: _matchVariableModel(
chain: null,
ofInterest: ['num?', 'num*'],
),
});
var s2 = s1.write(objectQVar, _Type('int'), h);
// It's ambiguous whether to promote to num? or num*, so we don't
// promote.
expect(s2, same(s1));
});
});
var s2 = s1.write(objectQVar, _Type('num?'), h);
// It's ambiguous whether to promote to num? or num*, but since the
// written type is exactly num?, we use that.
expect(s2.variableInfo, {
objectQVar:
_matchVariableModel(chain: ['num?'], ofInterest: ['num?', 'num*'])

test('exact match', () {
var h = _Harness();
var s1 = FlowModel<_Var, _Type>(true)
.declare(objectQVar, true)
.tryPromote(h, objectQVar, _Type('num?'))
.ifFalse
.tryPromote(h, objectQVar, _Type('num*'))
.ifFalse;
expect(s1.variableInfo, {
objectQVar: _matchVariableModel(
chain: null,
ofInterest: ['num?', 'num*'],
),
});
var s2 = s1.write(objectQVar, _Type('num?'), h);
// It's ambiguous whether to promote to num? or num*, but since the
// written type is exactly num?, we use that.
expect(s2.variableInfo, {
objectQVar: _matchVariableModel(
chain: ['num?'],
ofInterest: ['num?', 'num*'],
),
});
});
});
});
Expand Down Expand Up @@ -2899,18 +3015,21 @@ class _Harness implements TypeOperations<_Var, _Type> {
'int <: Object?': true,
'int <: String': false,
'int? <: int': false,
'int? <: num': false,
'int? <: num?': true,
'int? <: Object?': true,
'num <: int': false,
'num <: Iterable': false,
'num <: List': false,
'num <: num?': true,
'num <: num*': true,
'num <: Object': true,
'num <: Object?': true,
'num? <: int?': false,
'num? <: num': false,
'num? <: num*': true,
'num? <: Object?': true,
'num* <: num': true,
'num* <: num?': true,
'num* <: Object?': true,
'Iterable <: int': false,
Expand Down

0 comments on commit 4814f00

Please sign in to comment.