Reduce the use of `<unnamed>` in analyzer error messages. Two error messages are affected: 1. When the user tries to access a static member of an extension through an instance of a class that mentions the extension's "on" type, and that extension is unnamed, we now report INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION instead of INSTANCE_ACCESS_TO_STATIC_MEMBER. The new error message is the same as the old one (and uses the same sharedName), but it omits the correction message suggesting to the user that they try accessing the static method directly via the extension name, since this advice doesn't apply. 2. When we report AMBIGUOUS_EXTENSION_MEMBER_ACCESS, if one of the ambiguous members comes from an unnamed extension, we now report the unnamed extension as "unnamed extension on '$type'" rather than referring it to as an extension named `<unnamed>`. Change-Id: I3ca3a1ccc9399b26b083040de20db8e4f691be32 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/217102 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart index 1871a08..f6c195c 100644 --- a/pkg/analyzer/lib/error/error.dart +++ b/pkg/analyzer/lib/error/error.dart
@@ -226,6 +226,7 @@ CompileTimeErrorCode.INITIALIZER_FOR_STATIC_FIELD, CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTENT_FIELD, CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, + CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY, CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_STATIC, CompileTimeErrorCode.INSTANTIATE_ABSTRACT_CLASS,
diff --git a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart index 3f5ac4f..6e59428 100644 --- a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
@@ -35,6 +35,9 @@ bool get _genericMetadataIsEnabled => _resolver.definingLibrary.featureSet.isEnabled(Feature.generic_metadata); + bool get _isNonNullableByDefault => + _resolver.definingLibrary.featureSet.isEnabled(Feature.non_nullable); + TypeProvider get _typeProvider => _resolver.typeProvider; TypeSystemImpl get _typeSystem => _resolver.typeSystem; @@ -73,9 +76,15 @@ nameEntity.length, [ name, - noneMoreSpecific - .map((e) => e.extension.name ?? '<unnamed>') - .quotedAndCommaSeparatedWithAnd, + noneMoreSpecific.map((e) { + var name = e.extension.name; + if (name != null) { + return "extension '$name'"; + } + var type = e.extension.extendedType + .getDisplayString(withNullability: _isNonNullableByDefault); + return "unnamed extension on '$type'"; + }).commaSeparatedWithAnd, ], ); return ResolutionResult.ambiguous;
diff --git a/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart index 386da13..d55bcaa 100644 --- a/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/function_reference_resolver.dart
@@ -164,14 +164,26 @@ [enclosingElement.displayName], ); } + } else if (enclosingElement is ExtensionElement && + enclosingElement.name == null) { + _resolver.errorReporter.reportErrorForNode( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + nameNode, + [ + nameNode.name, + element.kind.displayName, + ]); } else { + // It is safe to assume that `enclosingElement.name` is non-`null` because + // it can only be `null` for extensions, and we handle that case above. _resolver.errorReporter.reportErrorForNode( CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, nameNode, [ nameNode.name, element.kind.displayName, - enclosingElement.name ?? '<unnamed>', + enclosingElement.name!, enclosingElement is ClassElement && enclosingElement.isMixin ? 'mixin' : enclosingElement.kind.displayName,
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart index e75d2be..7070eac 100644 --- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -234,14 +234,26 @@ [enclosingElement.displayName], ); } + } else if (enclosingElement is ExtensionElement && + enclosingElement.name == null) { + _resolver.errorReporter.reportErrorForNode( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + nameNode, + [ + nameNode.name, + element.kind.displayName, + ]); } else { + // It is safe to assume that `enclosingElement.name` is non-`null` because + // it can only be `null` for extensions, and we handle that case above. _resolver.errorReporter.reportErrorForNode( CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, nameNode, [ nameNode.name, element.kind.displayName, - enclosingElement.name ?? '<unnamed>', + enclosingElement.name!, enclosingElement is ClassElement && enclosingElement.isMixin ? 'mixin' : enclosingElement.kind.displayName,
diff --git a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart index 787d963..84cc019 100644 --- a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart +++ b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -275,16 +275,31 @@ ); } else { var enclosingElement = element.enclosingElement; - _errorReporter.reportErrorForNode( - CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, - propertyName, [ - propertyName, - element.kind.displayName, - enclosingElement.name ?? '<unnamed>', - enclosingElement is ClassElement && enclosingElement.isMixin - ? 'mixin' - : enclosingElement.kind.displayName, - ]); + if (enclosingElement is ExtensionElement && + enclosingElement.name == null) { + _resolver.errorReporter.reportErrorForNode( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + propertyName, + [ + propertyName.name, + element.kind.displayName, + ]); + } else { + // It is safe to assume that `enclosingElement.name` is non-`null` + // because it can only be `null` for extensions, and we handle that + // case above. + _errorReporter.reportErrorForNode( + CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, + propertyName, [ + propertyName.name, + element.kind.displayName, + enclosingElement.name!, + enclosingElement is ClassElement && enclosingElement.isMixin + ? 'mixin' + : enclosingElement.kind.displayName, + ]); + } } } }
diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart index 8391168..50bdab7 100644 --- a/pkg/analyzer/lib/src/error/codes.g.dart +++ b/pkg/analyzer/lib/src/error/codes.g.dart
@@ -184,8 +184,7 @@ /** * Parameters: * 0: the name of the member - * 1: the name of the first declaring extension - * 2: the name of the second declaring extension + * 1: the names of the declaring extensions */ // #### Description // @@ -244,7 +243,7 @@ static const CompileTimeErrorCode AMBIGUOUS_EXTENSION_MEMBER_ACCESS = CompileTimeErrorCode( 'AMBIGUOUS_EXTENSION_MEMBER_ACCESS', - "A member named '{0}' is defined in extensions {1}, and none are more specific.", + "A member named '{0}' is defined in {1}, and none are more specific.", correctionMessage: "Try using an extension override to specify the extension you want to be chosen.", hasPublishedDocs: true, @@ -6253,6 +6252,20 @@ ); /** + * Parameters: + * 0: the name of the static member + * 1: the kind of the static member (field, getter, setter, or method) + */ + static const CompileTimeErrorCode + INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION = + CompileTimeErrorCode( + 'INSTANCE_ACCESS_TO_STATIC_MEMBER', + "The static {1} '{0}' can't be accessed through an instance.", + hasPublishedDocs: true, + uniqueName: 'INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION', + ); + + /** * No parameters. */ // #### Description
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 796ec2c..b496a4f 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml
@@ -305,14 +305,13 @@ export 'b.dart' hide C; ``` AMBIGUOUS_EXTENSION_MEMBER_ACCESS: - problemMessage: "A member named '{0}' is defined in extensions {1}, and none are more specific." + problemMessage: "A member named '{0}' is defined in {1}, and none are more specific." correctionMessage: Try using an extension override to specify the extension you want to be chosen. hasPublishedDocs: true comment: |- Parameters: 0: the name of the member - 1: the name of the first declaring extension - 2: the name of the second declaring extension + 1: the names of the declaring extensions documentation: |- #### Description @@ -5542,6 +5541,14 @@ static int zero = 0; } ``` + INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION: + sharedName: INSTANCE_ACCESS_TO_STATIC_MEMBER + problemMessage: "The static {1} '{0}' can't be accessed through an instance." + hasPublishedDocs: true + comment: |- + Parameters: + 0: the name of the static member + 1: the kind of the static member (field, getter, setter, or method) INSTANCE_MEMBER_ACCESS_FROM_FACTORY: problemMessage: "Instance members can't be accessed from a factory constructor." correctionMessage: Try removing the reference to the instance member.
diff --git a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart index 0238785..1a2adbb 100644 --- a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart
@@ -444,8 +444,11 @@ static void m<T>(T t) {} } ''', [ - error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 40, 1, - correctionContains: "extension '<unnamed>'"), + error( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + 40, + 1), ]); assertFunctionReference(findNode.functionReference('foo.m<int>;'),
diff --git a/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart b/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart index 6355201..4c7e2f7 100644 --- a/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart +++ b/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart
@@ -106,7 +106,7 @@ } ''', [ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3, - messageContains: "'E1' and 'E2'"), + messageContains: "in extension 'E1' and extension 'E2',"), ]); } @@ -131,7 +131,7 @@ } ''', [ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3, - messageContains: "'E1' and 'E2'"), + messageContains: "in extension 'E1' and extension 'E2',"), ]); } @@ -177,7 +177,7 @@ } ''', [ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3, - messageContains: "'E1' and 'E2'"), + messageContains: "in extension 'E1' and extension 'E2',"), ]); } @@ -192,7 +192,8 @@ } ''', [ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 167, 3, - messageContains: "'E1', 'E2', and 'E3'"), + messageContains: + "in extension 'E1', extension 'E2', and extension 'E3',"), ]); } @@ -346,4 +347,31 @@ assertTypeDynamic(access); } } + + test_unnamed_extensions() async { + await assertErrorsInCode(''' +class A {} +class B {} +class C extends A implements B {} + +extension on List<A> { + int call() => 0; +} + +extension on List<B> { + int call() => 0; +} + +int f(List<C> x) => x(); + +// Additional calls to avoid UNUSED_ELEMENT +int g(List<A> x) => x(); +int h(List<B> x) => x(); +''', [ + error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 167, 1, + messageContains: + "in unnamed extension on 'List<A>' and unnamed extension on " + "'List<B>',"), + ]); + } }
diff --git a/pkg/analyzer/test/src/diagnostics/instance_access_to_static_member_test.dart b/pkg/analyzer/test/src/diagnostics/instance_access_to_static_member_test.dart index 1142a36..620441c 100644 --- a/pkg/analyzer/test/src/diagnostics/instance_access_to_static_member_test.dart +++ b/pkg/analyzer/test/src/diagnostics/instance_access_to_static_member_test.dart
@@ -56,6 +56,31 @@ ); } + test_extension_getter_unnamed() async { + await assertErrorsInCode(''' +class C {} + +extension on C { + static int get a => 0; +} + +C g(C c) => C(); +f(C c) { + g(c).a; +} +''', [ + error( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + 90, + 1), + ]); + assertElement( + findNode.simple('a;'), + findElement.getter('a'), + ); + } + test_extension_method() async { await assertErrorsInCode(''' class C {} @@ -89,8 +114,11 @@ c.a(); } ''', [ - error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 66, 1, - correctionContains: "extension '<unnamed>'"), + error( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + 66, + 1), ]); assertElement( findNode.methodInvocation('a();'), @@ -187,8 +215,11 @@ a.m<int>; } ''', [ - error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 55, 1, - correctionContains: "extension '<unnamed>'"), + error( + CompileTimeErrorCode + .INSTANCE_ACCESS_TO_STATIC_MEMBER_OF_UNNAMED_EXTENSION, + 55, + 1), ]); }
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md index 5e420b4..14c6b3e 100644 --- a/pkg/analyzer/tool/diagnostics/diagnostics.md +++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -398,7 +398,7 @@ ### ambiguous_extension_member_access -_A member named '{0}' is defined in extensions {1}, and none are more specific._ +_A member named '{0}' is defined in {1}, and none are more specific._ #### Description