From 1aa398d4be362d7e33d1ac3f3150ff4970e3a508 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Wed, 21 Sep 2022 02:24:45 +0000 Subject: [PATCH] Report EXPECTED_NAMED_TYPE_X when not a NamedType is parsed. Change-Id: Ibebc276777075da12e36642f08291fa9a9f9ac91 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260073 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../services/correction/error_fix_status.yaml | 8 ++ pkg/analyzer/lib/error/error.dart | 4 + .../src/dart/error/syntactic_errors.g.dart | 30 +++++ pkg/analyzer/lib/src/fasta/ast_builder.dart | 76 +++++++----- pkg/analyzer/messages.yaml | 16 +++ .../test/src/fasta/ast_builder_test.dart | 115 +++++++++++++----- 6 files changed, 188 insertions(+), 61 deletions(-) diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index 275975a2f0c..58d7581d5aa 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -2167,6 +2167,14 @@ ParserErrorCode.EXPECTED_INSTEAD: status: needsEvaluation ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL: status: needsEvaluation +ParserErrorCode.EXPECTED_NAMED_TYPE_EXTENDS: + status: noFix +ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS: + status: noFix +ParserErrorCode.EXPECTED_NAMED_TYPE_ON: + status: noFix +ParserErrorCode.EXPECTED_NAMED_TYPE_WITH: + status: noFix ParserErrorCode.EXPECTED_STRING_LITERAL: status: needsEvaluation ParserErrorCode.EXPECTED_TOKEN: diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart index 482cd911d86..5b74698b7eb 100644 --- a/pkg/analyzer/lib/error/error.dart +++ b/pkg/analyzer/lib/error/error.dart @@ -758,6 +758,10 @@ const List errorCodeValues = [ ParserErrorCode.EXPECTED_IDENTIFIER_BUT_GOT_KEYWORD, ParserErrorCode.EXPECTED_INSTEAD, ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL, + ParserErrorCode.EXPECTED_NAMED_TYPE_EXTENDS, + ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS, + ParserErrorCode.EXPECTED_NAMED_TYPE_ON, + ParserErrorCode.EXPECTED_NAMED_TYPE_WITH, ParserErrorCode.EXPECTED_STRING_LITERAL, ParserErrorCode.EXPECTED_TOKEN, ParserErrorCode.EXPECTED_TYPE_NAME, diff --git a/pkg/analyzer/lib/src/dart/error/syntactic_errors.g.dart b/pkg/analyzer/lib/src/dart/error/syntactic_errors.g.dart index 1f9467d3d4b..f44efccb398 100644 --- a/pkg/analyzer/lib/src/dart/error/syntactic_errors.g.dart +++ b/pkg/analyzer/lib/src/dart/error/syntactic_errors.g.dart @@ -506,6 +506,36 @@ class ParserErrorCode extends ErrorCode { "Try inserting a list or map literal, or remove the type arguments.", ); + static const ParserErrorCode EXPECTED_NAMED_TYPE_EXTENDS = ParserErrorCode( + 'EXPECTED_NAMED_TYPE', + "Expected a class name.", + correctionMessage: "Try using a class name, possibly with type arguments.", + uniqueName: 'EXPECTED_NAMED_TYPE_EXTENDS', + ); + + static const ParserErrorCode EXPECTED_NAMED_TYPE_IMPLEMENTS = ParserErrorCode( + 'EXPECTED_NAMED_TYPE', + "Expected the name of a class or mixin.", + correctionMessage: + "Try using a class or mixin name, possibly with type arguments.", + uniqueName: 'EXPECTED_NAMED_TYPE_IMPLEMENTS', + ); + + static const ParserErrorCode EXPECTED_NAMED_TYPE_ON = ParserErrorCode( + 'EXPECTED_NAMED_TYPE', + "Expected the name of a class or mixin.", + correctionMessage: + "Try using a class or mixin name, possibly with type arguments.", + uniqueName: 'EXPECTED_NAMED_TYPE_ON', + ); + + static const ParserErrorCode EXPECTED_NAMED_TYPE_WITH = ParserErrorCode( + 'EXPECTED_NAMED_TYPE', + "Expected a mixin name.", + correctionMessage: "Try using a mixin name, possibly with type arguments.", + uniqueName: 'EXPECTED_NAMED_TYPE_WITH', + ); + static const ParserErrorCode EXPECTED_STRING_LITERAL = ParserErrorCode( 'EXPECTED_STRING_LITERAL', "Expected a string literal.", diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart index e29313bb910..318b90c4fe2 100644 --- a/pkg/analyzer/lib/src/fasta/ast_builder.dart +++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart @@ -56,11 +56,13 @@ import 'package:_fe_analyzer_shared/src/scanner/token_constants.dart'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart' show Token, TokenType; +import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:analyzer/src/dart/analysis/experiments.dart'; import 'package:analyzer/src/dart/ast/ast.dart'; import 'package:analyzer/src/dart/ast/ast_factory.dart'; +import 'package:analyzer/src/dart/error/syntactic_errors.dart'; import 'package:analyzer/src/fasta/error_converter.dart'; import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:analyzer/src/summary2/ast_binary_tokens.dart'; @@ -2381,7 +2383,9 @@ class AstBuilder extends StackListener { ImplementsClauseImpl? implementsClause; if (implementsKeyword != null) { - var interfaces = popTypeList(); + var interfaces = _popNamedTypeList( + errorCode: ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS, + ); implementsClause = ImplementsClauseImpl( implementsKeyword: implementsKeyword, interfaces: interfaces, @@ -3221,12 +3225,16 @@ class AstBuilder extends StackListener { ), ); } else { - // TODO(brianwilkerson) Produce a diagnostic indicating that the type - // annotation is either missing or an invalid kind. Also, consider - // (a) extending `ExtendsClause` to accept any type annotation for - // recovery purposes, and (b) extending the parser to parse a generic - // function type at this location. push(NullValue.ExtendsClause); + // TODO(brianwilkerson) Consider (a) extending `ExtendsClause` to accept + // any type annotation for recovery purposes, and (b) extending the + // parser to parse a generic function type at this location. + if (supertype != null) { + errorReporter.errorReporter?.reportErrorForNode( + ParserErrorCode.EXPECTED_NAMED_TYPE_EXTENDS, + supertype, + ); + } } } @@ -3281,7 +3289,9 @@ class AstBuilder extends StackListener { @override void handleClassWithClause(Token withKeyword) { assert(optional('with', withKeyword)); - var mixinTypes = popTypeList(); + var mixinTypes = _popNamedTypeList( + errorCode: ParserErrorCode.EXPECTED_NAMED_TYPE_WITH, + ); push(ast.withClause(withKeyword, mixinTypes)); } @@ -3524,7 +3534,9 @@ class AstBuilder extends StackListener { @override void handleEnumWithClause(Token withKeyword) { assert(optional('with', withKeyword)); - var mixinTypes = popTypeList(); + var mixinTypes = _popNamedTypeList( + errorCode: ParserErrorCode.EXPECTED_NAMED_TYPE_WITH, + ); push(ast.withClause(withKeyword, mixinTypes)); } @@ -3781,9 +3793,10 @@ class AstBuilder extends StackListener { debugEvent("Implements"); if (implementsKeyword != null) { - var types = popTypedList2(interfacesCount); - // TODO(brianwilkerson) Report diagnostics for any type that's filtered out. - var interfaces = types.whereType().toList(); + endTypeList(interfacesCount); + final interfaces = _popNamedTypeList( + errorCode: ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS, + ); push( ImplementsClauseImpl( implementsKeyword: implementsKeyword, @@ -4086,9 +4099,10 @@ class AstBuilder extends StackListener { debugEvent("MixinOn"); if (onKeyword != null) { - var types = popTypedList2(typeCount); - // TODO(brianwilkerson) Report diagnostics for any type that's filtered out. - var onTypes = types.whereType().toList(); + endTypeList(typeCount); + final onTypes = _popNamedTypeList( + errorCode: ParserErrorCode.EXPECTED_NAMED_TYPE_ON, + ); push(ast.onClause(onKeyword, onTypes)); } else { push(NullValue.IdentifierList); @@ -4116,7 +4130,9 @@ class AstBuilder extends StackListener { @override void handleNamedMixinApplicationWithClause(Token withKeyword) { assert(optionalOrNull('with', withKeyword)); - var mixinTypes = popTypeList(); + var mixinTypes = _popNamedTypeList( + errorCode: ParserErrorCode.EXPECTED_NAMED_TYPE_WITH, + ); push(ast.withClause(withKeyword, mixinTypes)); } @@ -4676,21 +4692,6 @@ class AstBuilder extends StackListener { return result.reversed.toList(); } - // List? popTypedList(int count, [List? list]) { - // if (count == 0) return null; - // assert(stack.length >= count); - // - // final tailList = list ?? List.filled(count, null, growable: true); - // stack.popList(count, tailList, null); - // return tailList; - // } - - List popTypeList() { - var types = pop() as List; - // TODO(brianwilkerson) Report diagnostics for any type that's filtered out. - return types.whereType().toList(); - } - void reportErrorIfNullableType(Token? questionMark) { if (questionMark != null) { assert(optional('?', questionMark)); @@ -4761,6 +4762,21 @@ class AstBuilder extends StackListener { typeArguments: typeArguments)); } + List _popNamedTypeList({ + required ErrorCode errorCode, + }) { + final types = pop() as List; + final namedTypes = []; + for (final type in types) { + if (type is NamedType) { + namedTypes.add(type); + } else { + errorReporter.errorReporter?.reportErrorForNode(errorCode, type); + } + } + return namedTypes; + } + void _reportFeatureNotEnabled({ required ExperimentalFeature feature, required Token startToken, diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 734618e7445..9234f5cb8d4 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -21892,6 +21892,22 @@ ParserErrorCode: EXPECTED_LIST_OR_MAP_LITERAL: problemMessage: Expected a list or map literal. correctionMessage: Try inserting a list or map literal, or remove the type arguments. + EXPECTED_NAMED_TYPE_EXTENDS: + sharedName: EXPECTED_NAMED_TYPE + problemMessage: Expected a class name. + correctionMessage: Try using a class name, possibly with type arguments. + EXPECTED_NAMED_TYPE_IMPLEMENTS: + sharedName: EXPECTED_NAMED_TYPE + problemMessage: Expected the name of a class or mixin. + correctionMessage: Try using a class or mixin name, possibly with type arguments. + EXPECTED_NAMED_TYPE_ON: + sharedName: EXPECTED_NAMED_TYPE + problemMessage: Expected the name of a class or mixin. + correctionMessage: Try using a class or mixin name, possibly with type arguments. + EXPECTED_NAMED_TYPE_WITH: + sharedName: EXPECTED_NAMED_TYPE + problemMessage: Expected a mixin name. + correctionMessage: Try using a mixin name, possibly with type arguments. EXPECTED_STRING_LITERAL: problemMessage: Expected a string literal. EXPECTED_TOKEN: diff --git a/pkg/analyzer/test/src/fasta/ast_builder_test.dart b/pkg/analyzer/test/src/fasta/ast_builder_test.dart index 37b1f230b51..c3eb403709d 100644 --- a/pkg/analyzer/test/src/fasta/ast_builder_test.dart +++ b/pkg/analyzer/test/src/fasta/ast_builder_test.dart @@ -213,7 +213,9 @@ ConstructorDeclaration var parseResult = parseStringWithErrors(r''' class C extends (int, int) {} '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_EXTENDS, 16, 10), + ]); final node = parseResult.findNode.classDeclaration('class C'); assertParsedNodeText( @@ -230,9 +232,11 @@ ClassDeclaration void test_class_implementsClause_recordType() { var parseResult = parseStringWithErrors(r''' -class C implements (int, int) {} +class C implements A, (int, int), B {} '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS, 22, 10), + ]); final node = parseResult.findNode.classDeclaration('class C'); assertParsedNodeText( @@ -243,8 +247,15 @@ ClassDeclaration name: C @6 implementsClause: ImplementsClause implementsKeyword: implements @8 - leftBracket: { @30 - rightBracket: } @31 + interfaces + NamedType + name: SimpleIdentifier + token: A @19 + NamedType + name: SimpleIdentifier + token: B @34 + leftBracket: { @36 + rightBracket: } @37 ''', withOffsets: true); } @@ -268,23 +279,29 @@ ClassDeclaration void test_class_withClause_recordType() { var parseResult = parseStringWithErrors(r''' -class C with (int, int) {} +class C with A, (int, int), B {} '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_WITH, 16, 10), + ]); final node = parseResult.findNode.classDeclaration('class C'); - assertParsedNodeText( - node, - r''' + assertParsedNodeText(node, r''' ClassDeclaration - classKeyword: class @0 - name: C @6 + classKeyword: class + name: C withClause: WithClause - withKeyword: with @8 - leftBracket: { @24 - rightBracket: } @25 -''', - withOffsets: true); + withKeyword: with + mixinTypes + NamedType + name: SimpleIdentifier + token: A + NamedType + name: SimpleIdentifier + token: B + leftBracket: { + rightBracket: } +'''); } void test_classAlias_macro() { @@ -316,10 +333,12 @@ ClassTypeAlias void test_classTypeAlias_implementsClause_recordType() { var parseResult = parseStringWithErrors(r''' -class C = Object with M implements (int, int); +class C = Object with M implements A, (int, int), B; mixin M {} '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS, 38, 10), + ]); final node = parseResult.findNode.classTypeAlias('class C'); assertParsedNodeText( @@ -340,16 +359,25 @@ ClassTypeAlias token: M @22 implementsClause: ImplementsClause implementsKeyword: implements @24 - semicolon: ; @45 + interfaces + NamedType + name: SimpleIdentifier + token: A @35 + NamedType + name: SimpleIdentifier + token: B @50 + semicolon: ; @51 ''', withOffsets: true); } void test_classTypeAlias_withClause_recordType() { var parseResult = parseStringWithErrors(r''' -class C = Object with (int, int); +class C = Object with A, (int, int), B; '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_WITH, 25, 10), + ]); final node = parseResult.findNode.classTypeAlias('class C'); assertParsedNodeText( @@ -364,7 +392,14 @@ ClassTypeAlias token: Object @10 withClause: WithClause withKeyword: with @17 - semicolon: ; @32 + mixinTypes + NamedType + name: SimpleIdentifier + token: A @22 + NamedType + name: SimpleIdentifier + token: B @37 + semicolon: ; @38 ''', withOffsets: true); } @@ -716,9 +751,11 @@ LibraryAugmentationDirective void test_mixin_implementsClause_recordType() { var parseResult = parseStringWithErrors(r''' class C {} -mixin M on C implements (int, int) {} +mixin M on C implements A, (int, int), B {} '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_IMPLEMENTS, 38, 10), + ]); final node = parseResult.findNode.mixinDeclaration('mixin M'); assertParsedNodeText( @@ -735,17 +772,26 @@ MixinDeclaration token: C @22 implementsClause: ImplementsClause implementsKeyword: implements @24 - leftBracket: { @46 - rightBracket: } @47 + interfaces + NamedType + name: SimpleIdentifier + token: A @35 + NamedType + name: SimpleIdentifier + token: B @50 + leftBracket: { @52 + rightBracket: } @53 ''', withOffsets: true); } void test_mixin_onClause_recordType() { var parseResult = parseStringWithErrors(r''' -mixin M on (int, int) {} +mixin M on A, (int, int), B {} '''); - parseResult.assertNoErrors(); + parseResult.assertErrors([ + error(ParserErrorCode.EXPECTED_NAMED_TYPE_ON, 14, 10), + ]); final node = parseResult.findNode.mixinDeclaration('mixin M'); assertParsedNodeText( @@ -756,8 +802,15 @@ MixinDeclaration name: M @6 onClause: OnClause onKeyword: on @8 - leftBracket: { @22 - rightBracket: } @23 + superclassConstraints + NamedType + name: SimpleIdentifier + token: A @11 + NamedType + name: SimpleIdentifier + token: B @26 + leftBracket: { @28 + rightBracket: } @29 ''', withOffsets: true); }