mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 10:33:28 +00:00
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:
parent
bb06e649f4
commit
72daafd70c
|
@ -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,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(']');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;'),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue