Fix correction message for CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER.

Previously, it assumed that the static member was always a class; it
could also be an extension or a mixin, and the extension could be
unnamed.  This change causes extensions (named and unnamed) to be
handled properly.

Handling mixins will be more complicated (since it will require
expanding ElementKind to include mixins), so that is not addressed
right now (see https://github.com/dart-lang/sdk/issues/47452).

Change-Id: Ie01a6abfdb560d97e545d36255909da19500b3c4
Bug: https://github.com/dart-lang/sdk/issues/47452
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/216680
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2021-10-14 22:13:55 +00:00 committed by commit-bot@chromium.org
parent bb06e649f4
commit 72daafd70c
11 changed files with 177 additions and 25 deletions

View file

@ -148,18 +148,19 @@ class FunctionReferenceResolver {
ExecutableElement element, {
required bool implicitReceiver,
}) {
var enclosingElement = element.enclosingElement;
if (_resolver.enclosingExtension != null) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode
.UNQUALIFIED_REFERENCE_TO_STATIC_MEMBER_OF_EXTENDED_TYPE,
nameNode,
[element.enclosingElement.displayName],
[enclosingElement.displayName],
);
} else if (implicitReceiver) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER,
nameNode,
[element.enclosingElement.displayName],
[enclosingElement.displayName],
);
} else {
_resolver.errorReporter.reportErrorForNode(
@ -168,7 +169,10 @@ class FunctionReferenceResolver {
[
nameNode.name,
element.kind.displayName,
element.enclosingElement.displayName,
enclosingElement.name ?? '<unnamed>',
enclosingElement is ClassElement && enclosingElement.isMixin
? 'mixin'
: enclosingElement.kind.displayName,
],
);
}

View file

@ -218,18 +218,19 @@ class MethodInvocationResolver {
ExecutableElement element,
bool nullReceiver,
) {
var enclosingElement = element.enclosingElement;
if (_resolver.enclosingExtension != null) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode
.UNQUALIFIED_REFERENCE_TO_STATIC_MEMBER_OF_EXTENDED_TYPE,
nameNode,
[element.enclosingElement.displayName],
[enclosingElement.displayName],
);
} else if (nullReceiver) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER,
nameNode,
[element.enclosingElement.displayName],
[enclosingElement.displayName],
);
} else {
_resolver.errorReporter.reportErrorForNode(
@ -238,7 +239,10 @@ class MethodInvocationResolver {
[
nameNode.name,
element.kind.displayName,
element.enclosingElement.displayName,
enclosingElement.name ?? '<unnamed>',
enclosingElement is ClassElement && enclosingElement.isMixin
? 'mixin'
: enclosingElement.kind.displayName,
],
);
}

View file

@ -6209,7 +6209,8 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
* Parameters:
* 0: the name of the static member
* 1: the kind of the static member (field, getter, setter, or method)
* 2: the name of the defining class
* 2: the name of the static member's enclosing element
* 3: the kind of the static member's enclosing element (class, mixin, or extension)
*/
// #### Description
//
@ -6247,8 +6248,8 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
static const CompileTimeErrorCode INSTANCE_ACCESS_TO_STATIC_MEMBER =
CompileTimeErrorCode(
'INSTANCE_ACCESS_TO_STATIC_MEMBER',
"Static {1} '{0}' can't be accessed through an instance.",
correctionMessage: "Try using the class '{2}' to access the {1}.",
"The static {1} '{0}' can't be accessed through an instance.",
correctionMessage: "Try using the {3} '{2}' to access the {1}.",
hasPublishedDocs: true,
);

View file

@ -2659,9 +2659,14 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
}
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER,
name,
[name.name, _getKind(element), element.enclosingElement.name]);
CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, name, [
name.name,
_getKind(element),
enclosingElement.name ?? '<unnamed>',
enclosingElement is ClassElement && enclosingElement.isMixin
? 'mixin'
: enclosingElement.kind.displayName
]);
}
}

View file

@ -5500,14 +5500,15 @@ CompileTimeErrorCode:
}
```
INSTANCE_ACCESS_TO_STATIC_MEMBER:
problemMessage: "Static {1} '{0}' can't be accessed through an instance."
correctionMessage: "Try using the class '{2}' to access the {1}."
problemMessage: "The static {1} '{0}' can't be accessed through an instance."
correctionMessage: "Try using the {3} '{2}' to access the {1}."
hasPublishedDocs: true
comment: |-
Parameters:
0: the name of the static member
1: the kind of the static member (field, getter, setter, or method)
2: the name of the defining class
2: the name of the static member's enclosing element
3: the kind of the static member's enclosing element (class, mixin, or extension)
documentation: |-
#### Description

View file

@ -2,6 +2,8 @@
// 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.
import 'dart:convert';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
@ -44,6 +46,10 @@ class ExpectedError {
/// The error code associated with the error.
final ErrorCode code;
// A pattern that should be contained in the error's correction message, or
// `null` if the correction message contents should not be checked.
final Pattern? correctionContains;
/// The offset of the beginning of the error's region.
final int offset;
@ -63,7 +69,8 @@ class ExpectedError {
/// Initialize a newly created error description.
ExpectedError(this.code, this.offset, this.length,
{this.message,
{this.correctionContains,
this.message,
this.messageContains,
this.expectedContextMessages = const <ExpectedContextMessage>[]});
@ -82,6 +89,10 @@ class ExpectedError {
error.message.contains(messageContains!) != true) {
return false;
}
if (correctionContains != null &&
!(error.correctionMessage ?? '').contains(correctionContains!)) {
return false;
}
List<DiagnosticMessage> contextMessages = error.contextMessages.toList();
contextMessages.sort((first, second) {
int result = first.filePath.compareTo(second.filePath);
@ -196,7 +207,9 @@ class GatheringErrorListener implements AnalysisErrorListener {
buffer.write(', ');
buffer.write(actual.length);
buffer.write(', ');
buffer.write(actual.message);
buffer.write(json.encode(actual.message));
buffer.write(', ');
buffer.write(json.encode(actual.correctionMessage));
buffer.writeln(']');
}
}

View file

@ -444,7 +444,8 @@ extension on Function {
static void m<T>(T t) {}
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 40, 1),
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 40, 1,
correctionContains: "extension '<unnamed>'"),
]);
assertFunctionReference(findNode.functionReference('foo.m<int>;'),

View file

@ -850,11 +850,13 @@ mixin ResolutionTest implements ResourceProviderMixin {
}
ExpectedError error(ErrorCode code, int offset, int length,
{String? text,
{Pattern? correctionContains,
String? text,
Pattern? messageContains,
List<ExpectedContextMessage> contextMessages =
const <ExpectedContextMessage>[]}) =>
ExpectedError(code, offset, length,
correctionContains: correctionContains,
message: text,
messageContains: messageContains,
expectedContextMessages: contextMessages);

View file

@ -19,11 +19,13 @@ class AbstractAnalysisOptionsTest {
}
ExpectedError error(ErrorCode code, int offset, int length,
{String? text,
{Pattern? correctionContains,
String? text,
Pattern? messageContains,
List<ExpectedContextMessage> contextMessages =
const <ExpectedContextMessage>[]}) =>
ExpectedError(code, offset, length,
correctionContains: correctionContains,
message: text,
messageContains: messageContains,
expectedContextMessages: contextMessages);

View file

@ -15,6 +15,25 @@ main() {
@reflectiveTest
class InstanceAccessToStaticMemberTest extends PubPackageResolutionTest {
test_class_method() async {
await assertErrorsInCode('''
class C {
static void a() {}
}
f(C c) {
c.a();
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 47, 1,
correctionContains: "class 'C'"),
]);
assertElement(
findNode.methodInvocation('a();'),
findElement.method('a'),
);
}
test_extension_getter() async {
await assertErrorsInCode('''
class C {}
@ -28,7 +47,8 @@ f(C c) {
g(c).a;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 92, 1),
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 92, 1,
correctionContains: "extension 'E'"),
]);
assertElement(
findNode.simple('a;'),
@ -48,7 +68,29 @@ f(C c) {
c.a();
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 68, 1),
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 68, 1,
correctionContains: "extension 'E'"),
]);
assertElement(
findNode.methodInvocation('a();'),
findElement.method('a'),
);
}
test_extension_method_unnamed() async {
await assertErrorsInCode('''
class C {}
extension on C {
static void a() {}
}
f(C c) {
c.a();
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 66, 1,
correctionContains: "extension '<unnamed>'"),
]);
assertElement(
findNode.methodInvocation('a();'),
@ -98,7 +140,50 @@ f(A a) {
a.m;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 41, 1),
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 41, 1,
correctionContains: "class 'A'"),
]);
}
test_method_reference_extension() async {
await assertErrorsInCode(r'''
extension E on int {
static m<T>() {}
}
f(int a) {
a.m<int>;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 57, 1,
correctionContains: "extension 'E'"),
]);
}
test_method_reference_extension_unnamed() async {
await assertErrorsInCode(r'''
extension on int {
static m<T>() {}
}
f(int a) {
a.m<int>;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 55, 1,
correctionContains: "extension '<unnamed>'"),
]);
}
test_method_reference_mixin() async {
await assertErrorsInCode(r'''
mixin A {
static m() {}
}
f(A a) {
a.m;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 41, 1,
correctionContains: "mixin 'A'"),
]);
}
@ -111,10 +196,44 @@ f(A a) {
a.m<int>;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 44, 1),
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 44, 1,
correctionContains: "class 'A'"),
]);
}
test_method_reference_typeInstantiation_mixin() async {
await assertErrorsInCode(r'''
mixin A {
static m<T>() {}
}
f(A a) {
a.m<int>;
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 44, 1,
correctionContains: "mixin 'A'"),
]);
}
test_mixin_method() async {
await assertErrorsInCode('''
mixin A {
static void a() {}
}
f(A a) {
a.a();
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 47, 1,
correctionContains: "mixin 'A'"),
]);
assertElement(
findNode.methodInvocation('a();'),
findElement.method('a'),
);
}
test_propertyAccess_field() async {
await assertErrorsInCode(r'''
class A {

View file

@ -6000,7 +6000,7 @@ class C {
### instance_access_to_static_member
_Static {1} '{0}' can't be accessed through an instance._
_The static {1} '{0}' can't be accessed through an instance._
#### Description