From 6eed35b60df87f3bb099cf9ce9db5cbccb9ec67a Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 5 Sep 2019 00:58:46 +0000 Subject: [PATCH] Analyze null-aware index expression reads. R=brianwilkerson@google.com Change-Id: Idcd787434eae070e87adf267a016bda2a4e5703e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/115600 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../lib/src/generated/error_verifier.dart | 5 +- .../src/generated/static_type_analyzer.dart | 38 ++++---- .../resolution/index_expression_test.dart | 86 +++++++++++++++++++ ...dot_index_produces_nullable_type_test.dart | 33 +++++++ 4 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 tests/language_2/nnbd/resolution/question_dot_index_produces_nullable_type_test.dart diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart index 84eed22e219..43037e22619 100644 --- a/pkg/analyzer/lib/src/generated/error_verifier.dart +++ b/pkg/analyzer/lib/src/generated/error_verifier.dart @@ -998,7 +998,10 @@ class ErrorVerifier extends RecursiveAstVisitor { @override void visitIndexExpression(IndexExpression node) { _checkForArgumentTypeNotAssignableForArgument(node.index); - _checkForNullableDereference(node.target); + if (node.leftBracket.type != + TokenType.QUESTION_PERIOD_OPEN_SQUARE_BRACKET) { + _checkForNullableDereference(node.target); + } super.visitIndexExpression(node); } diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart index bed731bddd2..9c306d6794f 100644 --- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart +++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart @@ -593,15 +593,25 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { */ @override void visitIndexExpression(IndexExpression node) { + DartType type; if (node.inSetterContext()) { - ExecutableElement staticMethodElement = node.staticElement; - DartType staticType = _computeArgumentType(staticMethodElement); - _recordStaticType(node, staticType); + var parameters = node.staticElement?.parameters; + if (parameters?.length == 2) { + type = parameters[1].type; + } } else { - ExecutableElement staticMethodElement = node.staticElement; - DartType staticType = _computeStaticReturnType(staticMethodElement); - _recordStaticType(node, staticType); + type = node.staticElement?.returnType; } + + type ??= _dynamicType; + if (_nonNullableEnabled) { + if (node.leftBracket.type == + TokenType.QUESTION_PERIOD_OPEN_SQUARE_BRACKET) { + type = _typeSystem.makeNullable(type); + } + } + + _recordStaticType(node, type); } /** @@ -1235,22 +1245,6 @@ class StaticTypeAnalyzer extends SimpleAstVisitor { } } - /** - * Record that the static type of the given node is the type of the second argument to the method - * represented by the given element. - * - * @param element the element representing the method invoked by the given node - */ - DartType _computeArgumentType(ExecutableElement element) { - if (element != null) { - List parameters = element.parameters; - if (parameters != null && parameters.length == 2) { - return parameters[1].type; - } - } - return _dynamicType; - } - DartType _computeElementType(CollectionElement element) { if (element is ForElement) { return _computeElementType(element.body); diff --git a/pkg/analyzer/test/src/dart/resolution/index_expression_test.dart b/pkg/analyzer/test/src/dart/resolution/index_expression_test.dart index e10e05b64c4..e76cb13c671 100644 --- a/pkg/analyzer/test/src/dart/resolution/index_expression_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/index_expression_test.dart @@ -195,4 +195,90 @@ class IndexExpressionWithNnbdTest extends IndexExpressionTest { AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl() ..contextFeatures = FeatureSet.forTesting( sdkVersion: '2.3.0', additionalFeatures: [Feature.non_nullable]); + + @override + bool get typeToStringWithNullability => true; + + test_read_nullable() async { + await assertNoErrorsInCode(r''' +class A { + bool operator[](int index) => false; +} + +main(A? a) { + a?.[0]; +} +'''); + + var indexElement = findElement.method('[]'); + + var indexExpression = findNode.index('a?.[0]'); + assertElement(indexExpression, indexElement); + assertAuxElement(indexExpression, null); + assertType(indexExpression, 'bool?'); + } + + @failingTest + test_readWrite_nullable() async { + await assertNoErrorsInCode(r''' +class A { + num operator[](int index) => 0; + void operator[]=(int index, num value) {} +} + +main(A? a) { + a?.[0] += 1.2; +} +'''); + + var indexElement = findElement.method('[]'); + var indexEqElement = findElement.method('[]='); + var numPlusElement = numElement.getMethod('+'); + + var indexExpression = findNode.index('a?.[0]'); + assertElement(indexExpression, indexEqElement); + assertAuxElement(indexExpression, indexElement); + assertParameterElement( + indexExpression.index, + indexEqElement.parameters[0], + ); + assertType(indexExpression, 'num?'); + + var assignment = indexExpression.parent as AssignmentExpression; + assertElement(assignment, numPlusElement); + assertType(assignment, 'num?'); + assertParameterElement( + assignment.rightHandSide, + numPlusElement.parameters[0], + ); + } + + @failingTest + test_write_nullable() async { + await assertNoErrorsInCode(r''' +class A { + void operator[]=(int index, num value) {} +} + +main(A? a) { + a?.[0] = 1.2; +} +'''); + + var indexEqElement = findElement.method('[]='); + + var indexExpression = findNode.index('a?.[0]'); + assertElement(indexExpression, indexEqElement); + assertAuxElement(indexExpression, null); + assertParameterElement( + indexExpression.index, + indexEqElement.parameters[0], + ); + assertType(indexExpression, 'num?'); + + var assignment = indexExpression.parent as AssignmentExpression; + assertElement(assignment, null); + assertType(assignment, 'double?'); + assertParameterElement(assignment.rightHandSide, null); + } } diff --git a/tests/language_2/nnbd/resolution/question_dot_index_produces_nullable_type_test.dart b/tests/language_2/nnbd/resolution/question_dot_index_produces_nullable_type_test.dart new file mode 100644 index 00000000000..b4ee37fbf62 --- /dev/null +++ b/tests/language_2/nnbd/resolution/question_dot_index_produces_nullable_type_test.dart @@ -0,0 +1,33 @@ +// Copyright (c) 2019, 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. + +// SharedOptions=--enable-experiment=non-nullable + +void main() {} + +void f1(NotGeneric x) { + x[0] + 1; //# 01: ok +} + +void f2(NotGeneric? x) { + x?.[0] + 1; //# 02: compile-time error +} + +void f3(Generic? x) { + x?.[0] + 1; //# 03: compile-time error +} + +void f4(Generic x) { + x[0] + 1; //# 04: compile-time error +} + +class NotGeneric { + int operator[](int index) => throw 'unreachable'; + void operator[]=(int index, int value) => throw 'unreachable'; +} + +class Generic { + T operator[](int index) => throw 'unreachable'; + void operator[]=(int index, T value) => throw 'unreachable'; +}