[analyzer] resolve x?.y to return T?
Change-Id: I3ef319621753364b8d70e79b2ab32d7ec9c1e8d8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/92383
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index e115b57..9502121 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -3776,7 +3776,7 @@
}
this.inferenceContext = new InferenceContext._(
typeProvider, typeSystem, strongModeHints, errorReporter);
- this.typeAnalyzer = new StaticTypeAnalyzer(this);
+ this.typeAnalyzer = new StaticTypeAnalyzer(this, featureSet);
}
/// Return the element representing the function containing the current node,
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index b110a681..ceca4ee 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -4,6 +4,7 @@
import 'dart:collection';
+import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
import 'package:analyzer/dart/ast/token.dart';
@@ -22,6 +23,7 @@
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/task/strong/checker.dart'
show getExpressionType, getReadType;
+import 'package:meta/meta.dart';
/**
* Instances of the class `StaticTypeAnalyzer` perform two type-related tasks. First, they
@@ -69,12 +71,14 @@
*/
TypePromotionManager _promoteManager;
+ bool nonNullableEnabled;
+
/**
* Initialize a newly created type analyzer.
*
* @param resolver the resolver driving this participant
*/
- StaticTypeAnalyzer(this._resolver) {
+ StaticTypeAnalyzer(this._resolver, FeatureSet featureSet) {
_typeProvider = _resolver.typeProvider;
_typeSystem = _resolver.typeSystem;
_dynamicType = _typeProvider.dynamicType;
@@ -82,6 +86,7 @@
AnalysisOptionsImpl analysisOptions =
_resolver.definingLibrary.context.analysisOptions;
_strictInference = analysisOptions.strictInference;
+ nonNullableEnabled = featureSet.isEnabled(Feature.non_nullable);
}
/**
@@ -502,7 +507,8 @@
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
_inferGenericInvocationExpression(node);
- DartType staticType = _computeInvokeReturnType(node.staticInvokeType);
+ DartType staticType = _computeInvokeReturnType(node.staticInvokeType,
+ isNullableInvoke: false);
_recordStaticType(node, staticType);
}
@@ -672,8 +678,9 @@
_inferMethodInvocationInlineJS(node);
if (!inferredStaticType) {
- DartType staticStaticType =
- _computeInvokeReturnType(node.staticInvokeType);
+ DartType staticStaticType = _computeInvokeReturnType(
+ node.staticInvokeType,
+ isNullableInvoke: node.operator?.type == TokenType.QUESTION_PERIOD);
_recordStaticType(node, staticStaticType);
}
}
@@ -777,6 +784,7 @@
} else if (staticElement is VariableElement) {
staticType = staticElement.type;
}
+
staticType = _inferGenericInstantiationFromContext(node, staticType);
if (!_inferObjectAccess(node, staticType, prefixedIdentifier)) {
_recordStaticType(prefixedIdentifier, staticType);
@@ -865,7 +873,13 @@
} else {
// TODO(brianwilkerson) Report this internal error.
}
+
+ if (node.operator.type == TokenType.QUESTION_PERIOD && nonNullableEnabled) {
+ staticType =
+ (staticType as TypeImpl).withNullability(NullabilitySuffix.question);
+ }
staticType = _inferGenericInstantiationFromContext(node, staticType);
+
if (!_inferObjectAccess(node, staticType, propertyName)) {
_recordStaticType(propertyName, staticType);
_recordStaticType(node, staticType);
@@ -1161,15 +1175,22 @@
* Compute the return type of the method or function represented by the given
* type that is being invoked.
*/
- DartType _computeInvokeReturnType(DartType type) {
+ DartType _computeInvokeReturnType(DartType type,
+ {@required bool isNullableInvoke}) {
+ TypeImpl returnType;
if (type is InterfaceType) {
MethodElement callMethod = type.lookUpMethod(
FunctionElement.CALL_METHOD_NAME, _resolver.definingLibrary);
- return callMethod?.type?.returnType ?? _dynamicType;
+ returnType = callMethod?.type?.returnType;
} else if (type is FunctionType) {
- return type.returnType ?? _dynamicType;
+ returnType = type.returnType;
}
- return _dynamicType;
+
+ if (isNullableInvoke && nonNullableEnabled) {
+ returnType = returnType?.withNullability(NullabilitySuffix.question);
+ }
+
+ return returnType ?? _dynamicType;
}
/**
@@ -1210,13 +1231,14 @@
//
FunctionType propertyType = element.type;
if (propertyType != null) {
- return _computeInvokeReturnType(propertyType.returnType);
+ return _computeInvokeReturnType(propertyType.returnType,
+ isNullableInvoke: false);
}
} else if (element is ExecutableElement) {
- return _computeInvokeReturnType(element.type);
+ return _computeInvokeReturnType(element.type, isNullableInvoke: false);
} else if (element is VariableElement) {
DartType variableType = _promoteManager.getStaticType(element);
- return _computeInvokeReturnType(variableType);
+ return _computeInvokeReturnType(variableType, isNullableInvoke: false);
}
return _dynamicType;
}
diff --git a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
index 820678a..089d66d 100644
--- a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
@@ -26,6 +26,35 @@
@override
bool get typeToStringWithNullability => true;
+ test_local_getterNullAwareAccess_interfaceType() async {
+ addTestFile(r'''
+m() {
+ int? x;
+ return x?.isEven;
+}
+''');
+
+ await resolveTestFile();
+ assertNoTestErrors();
+ assertType(findNode.propertyAccess('x?.isEven'), 'bool?');
+ }
+
+ test_local_methodNullAwareCall_interfaceType() async {
+ await addTestFile(r'''
+class C {
+ bool x() => true;
+}
+m() {
+ C? c;
+ return c?.x();
+}
+''');
+
+ await resolveTestFile();
+ assertNoTestErrors();
+ assertType(findNode.methodInvocation('c?.x()'), 'bool?');
+ }
+
test_local_parameter_interfaceType() async {
addTestFile('''
main() {
diff --git a/tests/language_2/nnbd/resolution/question_dot_produces_nullable_type_test.dart b/tests/language_2/nnbd/resolution/question_dot_produces_nullable_type_test.dart
new file mode 100644
index 0000000..bfb9388
--- /dev/null
+++ b/tests/language_2/nnbd/resolution/question_dot_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
+
+bool c = false;
+
+// Test that the type produced from expression `x?.y` is nullable.
+void main() {
+ int? x;
+ x?.bitLength + 1; //# 01: compile-time error
+ x?.round(2) + 1; //# 02: compile-time error
+}
+
+// Ensure it works correctly on type parameters
+void f<T extends num>(Generic<T>? generic, Generic<T?> nullableGeneric) {
+ generic?.getter + 1; //# 03: compile-time error
+ generic?.method() + 1; //# 04: compile-time error
+ generic?.nullableGetter + 1; //# 05: compile-time error
+ generic?.nullableMethod() + 1; //# 06: compile-time error
+ nullableGeneric?.getter + 1; //# 07: compile-time error
+ nullableGeneric?.method() + 1; //# 08: compile-time error
+ nullableGeneric?.nullableGetter + 1; //# 09: compile-time error
+ nullableGeneric?.nullableMethod() + 1; //# 10: compile-time error
+}
+
+class Generic<T> {
+ T get getter => throw Exception('unreachable');
+ T method() => throw Exception('unreachable');
+ T? nullableGetter = null;
+ T? nullableMethod() => null;
+}