Skip to content

Commit c2eb847

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Add "why not promoted" support for method invocations to CFE.
This required moving the logic for computing the context messages from the inference visitor to the type inferrer. This includes support for extension method invocations and invocation of `.call` on a function type. Bug: #44898 Change-Id: Icda160f69cc815953c43edc92be910c5f49f9401 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/184842 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent e0fd546 commit c2eb847

File tree

5 files changed

+184
-92
lines changed

5 files changed

+184
-92
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) 2020, 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+
// This test contains a test case for each kind of error that can arise from an
6+
// expression being nullable, for which we wish to report "why not promoted"
7+
// errors.
8+
9+
class C {
10+
int? i;
11+
void Function()? f;
12+
}
13+
14+
extension on int {
15+
get propertyOnNonNullInt => null;
16+
void methodOnNonNullInt() {}
17+
}
18+
19+
extension on int? {
20+
get propertyOnNullableInt => null;
21+
void methodOnNullableInt() {}
22+
}
23+
24+
property_get_of_variable(int? i, int? j) {
25+
if (i == null) return;
26+
/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i = j;
27+
i. /*notPromoted(explicitWrite)*/ isEven;
28+
}
29+
30+
extension_property_get_of_variable(int? i, int? j) {
31+
if (i == null) return;
32+
/*cfe.update: explicitWrite*/ /*analyzer.explicitWrite*/ i = j;
33+
i.propertyOnNullableInt;
34+
i.
35+
/*cfe.invoke: notPromoted(explicitWrite)*/
36+
/*analyzer.notPromoted(explicitWrite)*/
37+
propertyOnNonNullInt;
38+
}
39+
40+
property_get_of_expression(C c) {
41+
if (c.i == null) return;
42+
c.i. /*notPromoted(propertyNotPromoted(member:C.i))*/ isEven;
43+
}
44+
45+
extension_property_get_of_expression(C c) {
46+
if (c.i == null) return;
47+
c.i.propertyOnNullableInt;
48+
c
49+
.i
50+
.
51+
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C.i))*/
52+
/*analyzer.notPromoted(propertyNotPromoted(member:C.i))*/
53+
propertyOnNonNullInt;
54+
}
55+
56+
method_invocation(C c) {
57+
if (c.i == null) return;
58+
c.i
59+
.
60+
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C.i))*/
61+
/*analyzer.notPromoted(propertyNotPromoted(member:C.i))*/
62+
abs();
63+
}
64+
65+
extension_method_invocation(C c) {
66+
if (c.i == null) return;
67+
c.i.methodOnNullableInt();
68+
c.i
69+
.
70+
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C.i))*/
71+
/*analyzer.notPromoted(propertyNotPromoted(member:C.i))*/
72+
methodOnNonNullInt();
73+
}
74+
75+
call_invocation(C c) {
76+
if (c.f == null) return;
77+
c.f
78+
.
79+
/*cfe.invoke: notPromoted(propertyNotPromoted(member:C.f))*/
80+
/*analyzer.notPromoted(propertyNotPromoted(member:C.f))*/
81+
call();
82+
}

pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart

Lines changed: 1 addition & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@
55
// @dart = 2.9
66

77
import 'dart:core' hide MapEntry;
8-
import 'dart:core' as core;
98

10-
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
11-
import 'package:_fe_analyzer_shared/src/testing/id.dart';
129
import 'package:_fe_analyzer_shared/src/util/link.dart';
1310
import 'package:front_end/src/api_prototype/lowering_predicates.dart';
14-
import 'package:front_end/src/testing/id_extractor.dart';
1511
import 'package:kernel/ast.dart'
1612
hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
1713
import 'package:kernel/src/legacy_erasure.dart';
@@ -4739,36 +4735,13 @@ class InferenceVisitor
47394735

47404736
readResult ??= new ExpressionInferenceResult(readType, read);
47414737
if (!inferrer.isTopLevel && readTarget.isNullable) {
4742-
Map<DartType, NonPromotionReason> whyNotPromoted =
4743-
inferrer.flowAnalysis?.whyNotPromoted(receiver);
4744-
List<LocatedMessage> context;
4745-
if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
4746-
_WhyNotPromotedVisitor whyNotPromotedVisitor =
4747-
new _WhyNotPromotedVisitor(inferrer, receiver);
4748-
for (core.MapEntry<DartType, NonPromotionReason> entry
4749-
in whyNotPromoted.entries) {
4750-
if (entry.key.isPotentiallyNullable) continue;
4751-
LocatedMessage message = entry.value.accept(whyNotPromotedVisitor);
4752-
if (inferrer.dataForTesting != null) {
4753-
String nonPromotionReasonText = entry.value.shortName;
4754-
if (whyNotPromotedVisitor.propertyReference != null) {
4755-
Id id = computeMemberId(whyNotPromotedVisitor.propertyReference);
4756-
nonPromotionReasonText += '($id)';
4757-
}
4758-
inferrer.dataForTesting.flowAnalysisResult
4759-
.nonPromotionReasons[read] = nonPromotionReasonText;
4760-
}
4761-
context = [message];
4762-
break;
4763-
}
4764-
}
47654738
readResult = inferrer.wrapExpressionInferenceResultInProblem(
47664739
readResult,
47674740
templateNullablePropertyAccessError.withArguments(
47684741
propertyName.text, receiverType, inferrer.isNonNullableByDefault),
47694742
read.fileOffset,
47704743
propertyName.text.length,
4771-
context: context);
4744+
context: inferrer.getWhyNotPromotedContext(receiver, read));
47724745
}
47734746
return readResult;
47744747
}
@@ -6988,66 +6961,6 @@ class InferenceVisitor
69886961
}
69896962
}
69906963

6991-
class _WhyNotPromotedVisitor
6992-
implements
6993-
NonPromotionReasonVisitor<LocatedMessage, Node, Expression,
6994-
VariableDeclaration> {
6995-
final TypeInferrerImpl inferrer;
6996-
6997-
final Expression receiver;
6998-
6999-
Member propertyReference;
7000-
7001-
_WhyNotPromotedVisitor(this.inferrer, this.receiver);
7002-
7003-
@override
7004-
LocatedMessage visitDemoteViaExplicitWrite(
7005-
DemoteViaExplicitWrite<VariableDeclaration, Expression> reason) {
7006-
if (inferrer.dataForTesting != null) {
7007-
inferrer.dataForTesting.flowAnalysisResult
7008-
.nonPromotionReasonTargets[reason.writeExpression] = reason.shortName;
7009-
}
7010-
int offset = reason.writeExpression.fileOffset;
7011-
return templateVariableCouldBeNullDueToWrite
7012-
.withArguments(reason.variable.name)
7013-
.withLocation(inferrer.helper.uri, offset, noLength);
7014-
}
7015-
7016-
@override
7017-
LocatedMessage visitDemoteViaForEachVariableWrite(
7018-
DemoteViaForEachVariableWrite<VariableDeclaration, Node> reason) {
7019-
int offset = (reason.node as TreeNode).fileOffset;
7020-
return templateVariableCouldBeNullDueToWrite
7021-
.withArguments(reason.variable.name)
7022-
.withLocation(inferrer.helper.uri, offset, noLength);
7023-
}
7024-
7025-
@override
7026-
LocatedMessage visitPropertyNotPromoted(PropertyNotPromoted reason) {
7027-
Member member;
7028-
Expression receiver = this.receiver;
7029-
if (receiver is InstanceGet) {
7030-
member = receiver.interfaceTarget;
7031-
} else if (receiver is SuperPropertyGet) {
7032-
member = receiver.interfaceTarget;
7033-
} else if (receiver is StaticInvocation) {
7034-
member = receiver.target;
7035-
} else if (receiver is PropertyGet) {
7036-
member = receiver.interfaceTarget;
7037-
} else {
7038-
assert(false, 'Unrecognized receiver: ${receiver.runtimeType}');
7039-
}
7040-
if (member != null) {
7041-
propertyReference = member;
7042-
return templateFieldNotPromoted
7043-
.withArguments(reason.propertyName)
7044-
.withLocation(member.fileUri, member.fileOffset, noLength);
7045-
} else {
7046-
return null;
7047-
}
7048-
}
7049-
}
7050-
70516964
class ForInResult {
70526965
final VariableDeclaration variable;
70536966
final Expression iterable;

pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
// @dart = 2.9
66

77
import 'dart:core' hide MapEntry;
8+
import 'dart:core' as core;
89

910
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'
1011
hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
1112

13+
import 'package:_fe_analyzer_shared/src/testing/id.dart';
1214
import 'package:_fe_analyzer_shared/src/util/link.dart';
1315

1416
import 'package:front_end/src/fasta/kernel/internal_ast.dart';
1517
import 'package:front_end/src/fasta/type_inference/type_demotion.dart';
18+
import 'package:front_end/src/testing/id_extractor.dart';
1619

1720
import 'package:kernel/ast.dart'
1821
hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
@@ -301,6 +304,37 @@ class TypeInferrerImpl implements TypeInferrer {
301304
..fileOffset = fileOffset;
302305
}
303306

307+
/// Computes a list of context messages explaining why [receiver] was not
308+
/// promoted, to be used when reporting an error for a larger expression
309+
/// containing [receiver]. [expression] is the containing expression.
310+
List<LocatedMessage> getWhyNotPromotedContext(
311+
Expression receiver, Expression expression) {
312+
Map<DartType, NonPromotionReason> whyNotPromoted =
313+
flowAnalysis?.whyNotPromoted(receiver);
314+
List<LocatedMessage> context;
315+
if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
316+
_WhyNotPromotedVisitor whyNotPromotedVisitor =
317+
new _WhyNotPromotedVisitor(this, receiver);
318+
for (core.MapEntry<DartType, NonPromotionReason> entry
319+
in whyNotPromoted.entries) {
320+
if (entry.key.isPotentiallyNullable) continue;
321+
LocatedMessage message = entry.value.accept(whyNotPromotedVisitor);
322+
if (dataForTesting != null) {
323+
String nonPromotionReasonText = entry.value.shortName;
324+
if (whyNotPromotedVisitor.propertyReference != null) {
325+
Id id = computeMemberId(whyNotPromotedVisitor.propertyReference);
326+
nonPromotionReasonText += '($id)';
327+
}
328+
dataForTesting.flowAnalysisResult.nonPromotionReasons[expression] =
329+
nonPromotionReasonText;
330+
}
331+
context = [message];
332+
break;
333+
}
334+
}
335+
return context;
336+
}
337+
304338
/// Returns `true` if exceptions should be thrown in paths reachable only due
305339
/// to unsoundness in flow analysis in mixed mode.
306340
bool get shouldThrowUnsoundnessException =>
@@ -2754,7 +2788,8 @@ class TypeInferrerImpl implements TypeInferrer {
27542788
templateNullableMethodCallError.withArguments(
27552789
name.text, receiverType, isNonNullableByDefault),
27562790
fileOffset,
2757-
name.text.length);
2791+
name.text.length,
2792+
context: getWhyNotPromotedContext(receiver, staticInvocation));
27582793
}
27592794
}
27602795
return createNullAwareExpressionInferenceResult(
@@ -2831,7 +2866,8 @@ class TypeInferrerImpl implements TypeInferrer {
28312866
templateNullableMethodCallError.withArguments(
28322867
callName.text, receiverType, isNonNullableByDefault),
28332868
fileOffset,
2834-
callName.text.length);
2869+
callName.text.length,
2870+
context: getWhyNotPromotedContext(receiver, expression));
28352871
}
28362872
}
28372873
// TODO(johnniwinther): Check that type arguments against the bounds.
@@ -2993,7 +3029,8 @@ class TypeInferrerImpl implements TypeInferrer {
29933029
templateNullableMethodCallError.withArguments(
29943030
methodName.text, receiverType, isNonNullableByDefault),
29953031
fileOffset,
2996-
methodName.text.length);
3032+
methodName.text.length,
3033+
context: getWhyNotPromotedContext(receiver, expression));
29973034
}
29983035
}
29993036

@@ -4836,3 +4873,63 @@ class InferredFunctionBody {
48364873

48374874
InferredFunctionBody(this.body, this.futureValueType);
48384875
}
4876+
4877+
class _WhyNotPromotedVisitor
4878+
implements
4879+
NonPromotionReasonVisitor<LocatedMessage, Node, Expression,
4880+
VariableDeclaration> {
4881+
final TypeInferrerImpl inferrer;
4882+
4883+
final Expression receiver;
4884+
4885+
Member propertyReference;
4886+
4887+
_WhyNotPromotedVisitor(this.inferrer, this.receiver);
4888+
4889+
@override
4890+
LocatedMessage visitDemoteViaExplicitWrite(
4891+
DemoteViaExplicitWrite<VariableDeclaration, Expression> reason) {
4892+
if (inferrer.dataForTesting != null) {
4893+
inferrer.dataForTesting.flowAnalysisResult
4894+
.nonPromotionReasonTargets[reason.writeExpression] = reason.shortName;
4895+
}
4896+
int offset = reason.writeExpression.fileOffset;
4897+
return templateVariableCouldBeNullDueToWrite
4898+
.withArguments(reason.variable.name)
4899+
.withLocation(inferrer.helper.uri, offset, noLength);
4900+
}
4901+
4902+
@override
4903+
LocatedMessage visitDemoteViaForEachVariableWrite(
4904+
DemoteViaForEachVariableWrite<VariableDeclaration, Node> reason) {
4905+
int offset = (reason.node as TreeNode).fileOffset;
4906+
return templateVariableCouldBeNullDueToWrite
4907+
.withArguments(reason.variable.name)
4908+
.withLocation(inferrer.helper.uri, offset, noLength);
4909+
}
4910+
4911+
@override
4912+
LocatedMessage visitPropertyNotPromoted(PropertyNotPromoted reason) {
4913+
Member member;
4914+
Expression receiver = this.receiver;
4915+
if (receiver is InstanceGet) {
4916+
member = receiver.interfaceTarget;
4917+
} else if (receiver is SuperPropertyGet) {
4918+
member = receiver.interfaceTarget;
4919+
} else if (receiver is StaticInvocation) {
4920+
member = receiver.target;
4921+
} else if (receiver is PropertyGet) {
4922+
member = receiver.interfaceTarget;
4923+
} else {
4924+
assert(false, 'Unrecognized receiver: ${receiver.runtimeType}');
4925+
}
4926+
if (member != null) {
4927+
propertyReference = member;
4928+
return templateFieldNotPromoted
4929+
.withArguments(reason.propertyName)
4930+
.withLocation(member.fileUri, member.fileOffset, noLength);
4931+
} else {
4932+
return null;
4933+
}
4934+
}
4935+
}

pkg/front_end/test/lint_test.status

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ front_end/lib/src/fasta/incremental_compiler/ImportsTwice: Fail
2020
front_end/lib/src/fasta/kernel/body_builder/ImportsTwice: Fail
2121
front_end/lib/src/fasta/kernel/constant_evaluator/ExplicitType: Pass
2222
front_end/lib/src/fasta/kernel/expression_generator_helper/ImportsTwice: Fail
23-
front_end/lib/src/fasta/kernel/inference_visitor/ImportsTwice: Fail
2423
front_end/lib/src/fasta/kernel/kernel_api/Exports: Fail
2524
front_end/lib/src/fasta/kernel/kernel_ast_api/Exports: Fail
2625
front_end/lib/src/fasta/kernel/kernel_builder/Exports: Fail

pkg/front_end/test/spell_checking_list_code.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ existentially
390390
exp
391391
expando
392392
expense
393+
explaining
393394
exportable
394395
exportee
395396
exportees

0 commit comments

Comments
 (0)