diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index ed7fc3c957de..61724a0b857b 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -4261,9 +4261,14 @@ class ResolverVisitor extends ThrowingAstVisitor case SimpleIdentifier(staticElement: InterfaceElement()): // `?.` to access static methods is equivalent to `.`, so do nothing. break; - default: - flow.nullAwareAccess_rightBegin(target, - SharedTypeView(target.staticType ?? typeProvider.dynamicType)); + case ExtensionOverride( + argumentList: ArgumentList(arguments: [var expression]) + ): + case var expression: + flow.nullAwareAccess_rightBegin( + expression, + SharedTypeView( + expression.staticType ?? typeProvider.dynamicType)); _unfinishedNullShorts.add(node.nullShortingTermination); } } diff --git a/pkg/analyzer/test/src/dart/resolution/extension_override_test.dart b/pkg/analyzer/test/src/dart/resolution/extension_override_test.dart index fe8108cdd764..73123afe565e 100644 --- a/pkg/analyzer/test/src/dart/resolution/extension_override_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/extension_override_test.dart @@ -6,10 +6,12 @@ import 'package:analyzer/src/error/codes.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'context_collection_resolution.dart'; +import 'node_text_expectations.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(ExtensionOverrideResolutionTest); + defineReflectiveTests(UpdateNodeTextExpectations); }); } @@ -1105,6 +1107,31 @@ BinaryExpression '''); } + test_promotion() async { + await assertNoErrorsInCode(r''' +class C { + int f() => 0; +} + +extension E on C { + int g(dynamic d) => 0; +} + +void test(C? c) { + E(c)?.g(c); // `c` is promoted to `C` on the RHS of `?.` +} +'''); + var node = findNode.simple('c);'); + assertResolvedNodeText(node, r''' +SimpleIdentifier + token: c + parameter: ::@extension::E::@method::g::@parameter::d + staticElement: ::@function::test::@parameter::c + element: ::@function::test::@parameter::c#element + staticType: C +'''); + } + test_propertyAccess_getter_nullAware() async { await assertNoErrorsInCode(''' extension E on int { diff --git a/tests/language/extension_methods/null_aware_promotion_test.dart b/tests/language/extension_methods/null_aware_promotion_test.dart new file mode 100644 index 000000000000..35dc25a573cd --- /dev/null +++ b/tests/language/extension_methods/null_aware_promotion_test.dart @@ -0,0 +1,71 @@ +// Copyright (c) 2024, 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. + +// This test verifies that a null-aware extension method invocation properly +// promotes its target to non-nullable. + +import '../static_type_helper.dart'; + +class B { + final C? _c; + B(this._c); +} + +class C { + void method(Object? o) {} + C operator +(Object? other) => this; +} + +extension E on C { + C extensionMethod(Object? o) => this; + C get extensionProperty => this; + set extensionProperty(Object? value) {} + C? get nullableExtensionProperty => this; + set nullableExtensionProperty(Object? value) {} + C operator [](Object? index) => this; + operator []=(Object? index, Object? value) {} +} + +extension E2 on C { + C? operator [](Object? index) => this; + operator []=(Object? index, Object? value) {} +} + +testVariable(C? c) { + E(c)?.extensionMethod(c..expectStaticType>()); + E(c)?.extensionProperty.method(c..expectStaticType>()); + E(c)?.extensionProperty = c..expectStaticType>(); + E(c)?.extensionProperty += c..expectStaticType>(); + E(c)?.nullableExtensionProperty ??= c..expectStaticType>(); + E(c)?[c + ..expectStaticType>()].method(c..expectStaticType>()); + E(c)?[c..expectStaticType>()] = c..expectStaticType>(); + E(c)?[c..expectStaticType>()] += c..expectStaticType>(); + E2(c)?[c..expectStaticType>()] ??= + c..expectStaticType>(); +} + +testProperty(B b) { + E(b._c)?.extensionMethod(b._c..expectStaticType>()); + E(b._c)?.extensionProperty.method(b._c..expectStaticType>()); + E(b._c)?.extensionProperty = b._c..expectStaticType>(); + E(b._c)?.extensionProperty += b._c..expectStaticType>(); + E(b._c)?.nullableExtensionProperty ??= b._c..expectStaticType>(); + E(b._c)?[b._c..expectStaticType>()].method( + b._c..expectStaticType>(), + ); + E(b._c)?[b._c..expectStaticType>()] = + b._c..expectStaticType>(); + E(b._c)?[b._c..expectStaticType>()] += + b._c..expectStaticType>(); + E2(b._c)?[b._c..expectStaticType>()] ??= + b._c..expectStaticType>(); +} + +main() { + for (var value in [null, C()]) { + testVariable(value); + testProperty(B(value)); + } +} diff --git a/tests/language/extension_methods/null_aware_reachability_test.dart b/tests/language/extension_methods/null_aware_reachability_test.dart new file mode 100644 index 000000000000..73a3e5a68de9 --- /dev/null +++ b/tests/language/extension_methods/null_aware_reachability_test.dart @@ -0,0 +1,79 @@ +// Copyright (c) 2024, 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. + +// This test verifies that a null-aware extension method invocation is properly +// treated as unreachable when the target has type `Null`. + +import '../static_type_helper.dart'; + +class C { + void method(Object? o) {} + C operator +(Object? other) => this; +} + +extension E on Null { + C extensionMethod(Object? o) => C(); + C get extensionProperty => C(); + set extensionProperty(Object? value) {} + C? get nullableExtensionProperty => C(); + set nullableExtensionProperty(Object? value) {} + C operator [](Object? index) => C(); + operator []=(Object? index, Object? value) {} +} + +extension E2 on C { + C? operator [](Object? index) => C(); + operator []=(Object? index, Object? value) {} +} + +testLiteralNull() { + int? i = 0; // Promotes to non-null. + i.expectStaticType>(); + E(null)?.extensionMethod(i = null); + i.expectStaticType>(); + E(null)?.extensionProperty.method(i = null); + i.expectStaticType>(); + E(null)?.extensionProperty = i = null; + i.expectStaticType>(); + E(null)?.extensionProperty += i = null; + i.expectStaticType>(); + E(null)?.nullableExtensionProperty ??= i = null; + i.expectStaticType>(); + E(null)?[i = null].method(i = null); + i.expectStaticType>(); + E(null)?[i = null] = i = null; + i.expectStaticType>(); + E(null)?[i = null] += i = null; + i.expectStaticType>(); + E2(null)?[i = null] ??= i = null; + i.expectStaticType>(); +} + +testNullVariable(Null n) { + int? i = 0; // Promotes to non-null. + i.expectStaticType>(); + E(n)?.extensionMethod(i = null); + i.expectStaticType>(); + E(n)?.extensionProperty.method(i = null); + i.expectStaticType>(); + E(n)?.extensionProperty = i = null; + i.expectStaticType>(); + E(n)?.extensionProperty += i = null; + i.expectStaticType>(); + E(n)?.nullableExtensionProperty ??= i = null; + i.expectStaticType>(); + E(n)?[i = null].method(i = null); + i.expectStaticType>(); + E(n)?[i = null] = i = null; + i.expectStaticType>(); + E(n)?[i = null] += i = null; + i.expectStaticType>(); + E2(n)?[i = null] ??= i = null; + i.expectStaticType>(); +} + +main() { + testLiteralNull(); + testNullVariable(null); +}