[analyzer/cfe] Report an error when declaring a mixin class with final, interface, or sealed.

Change-Id: Ia1393851dffaab55c31d04d4e81bfae83a7bd67f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283126
Commit-Queue: Kallen Tu <kallentu@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Kallen Tu 2023-02-16 21:27:28 +00:00 committed by Commit Queue
parent 71bbeddf00
commit f17e9732c5
19 changed files with 437 additions and 8 deletions

View file

@ -5296,6 +5296,15 @@ Message _withArgumentsFinalFieldWithoutInitializer(String name) {
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeFinalMixinClass = messageFinalMixinClass;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageFinalMixinClass = const MessageCode("FinalMixinClass",
index: 142,
problemMessage: r"""A mixin class can't be declared 'final'.""",
correctionMessage: r"""Try removing the 'final' keyword.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
@ -6418,6 +6427,16 @@ Message _withArgumentsInterfaceClassExtendedOutsideOfLibrary(String name) {
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeInterfaceMixinClass = messageInterfaceMixinClass;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageInterfaceMixinClass = const MessageCode(
"InterfaceMixinClass",
index: 143,
problemMessage: r"""A mixin class can't be declared 'interface'.""",
correctionMessage: r"""Try removing the 'interface' keyword.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(
@ -11306,6 +11325,16 @@ Message _withArgumentsSealedClassSubtypeOutsideOfLibrary(String name) {
arguments: {'name': name});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeSealedMixinClass = messageSealedMixinClass;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode messageSealedMixinClass = const MessageCode(
"SealedMixinClass",
index: 144,
problemMessage: r"""A mixin class can't be declared 'sealed'.""",
correctionMessage: r"""Try removing the 'sealed' keyword.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<
Message Function(

View file

@ -568,7 +568,7 @@ class Parser {
optional('class', next.next!)) {
macroToken = next;
next = next.next!;
} else if (next.isIdentifier && next.lexeme == 'sealed') {
} else if (next.isIdentifier && optional('sealed', next)) {
sealedToken = next;
if (optional('class', next.next!) || optional('mixin', next.next!)) {
next = next.next!;
@ -579,12 +579,12 @@ class Parser {
start = next;
next = next.next!.next!;
}
} else if (next.isIdentifier && next.lexeme == 'base') {
} else if (next.isIdentifier && optional('base', next)) {
baseToken = next;
if (optional('class', next.next!) || optional('mixin', next.next!)) {
next = next.next!;
}
} else if (next.isIdentifier && next.lexeme == 'interface') {
} else if (next.isIdentifier && optional('interface', next)) {
interfaceToken = next;
if (optional('class', next.next!) || optional('mixin', next.next!)) {
next = next.next!;
@ -712,8 +712,6 @@ class Parser {
return parseTypedef(keyword);
} else if (identical(value, 'mixin')) {
if (identical(nextValue, 'class')) {
// TODO(kallentu): Error handling for any class modifier here other
// than base. Only base mixin classes are allowed.
return _handleModifiersForClassDeclaration(
start,
keyword.next!,
@ -721,7 +719,7 @@ class Parser {
inlineToken,
sealedToken,
baseToken,
/* interfaceToken = */ null,
interfaceToken,
keyword,
directiveState);
}
@ -767,6 +765,19 @@ class Parser {
ModifierContext context = new ModifierContext(this);
if (mixinToken != null) {
context.parseClassModifiers(start, mixinToken);
// Mixin classes can't have any modifier other than a base modifier.
if (context.finalToken != null) {
reportRecoverableError(
context.finalToken!, codes.messageFinalMixinClass);
}
if (interfaceToken != null) {
reportRecoverableError(
interfaceToken, codes.messageInterfaceMixinClass);
}
if (sealedToken != null) {
reportRecoverableError(sealedToken, codes.messageSealedMixinClass);
}
} else {
context.parseClassModifiers(start, classKeyword);
}

View file

@ -2247,6 +2247,8 @@ ParserErrorCode.FINAL_ENUM:
status: needsEvaluation
ParserErrorCode.FINAL_METHOD:
status: needsEvaluation
ParserErrorCode.FINAL_MIXIN_CLASS:
status: needsEvaluation
ParserErrorCode.FINAL_TYPEDEF:
status: needsEvaluation
ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR:
@ -2269,6 +2271,8 @@ ParserErrorCode.IMPORT_DIRECTIVE_AFTER_PART_DIRECTIVE:
status: needsEvaluation
ParserErrorCode.INITIALIZED_VARIABLE_IN_FOR_EACH:
status: needsEvaluation
ParserErrorCode.INTERFACE_MIXIN_CLASS:
status: needsEvaluation
ParserErrorCode.INVALID_AWAIT_IN_FOR:
status: needsEvaluation
ParserErrorCode.INVALID_CODE_POINT:
@ -2455,6 +2459,8 @@ ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY:
status: needsEvaluation
ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR:
status: needsEvaluation
ParserErrorCode.SEALED_MIXIN_CLASS:
status: needsEvaluation
ParserErrorCode.SETTER_CONSTRUCTOR:
status: needsEvaluation
ParserErrorCode.SETTER_IN_FUNCTION:

View file

@ -156,6 +156,9 @@ final fastaAnalyzerErrorCodes = <ErrorCode?>[
ParserErrorCode.INVALID_CONSTANT_PATTERN_GENERIC,
ParserErrorCode.INVALID_CONSTANT_CONST_PREFIX,
ParserErrorCode.INVALID_CONSTANT_PATTERN_BINARY,
ParserErrorCode.FINAL_MIXIN_CLASS,
ParserErrorCode.INTERFACE_MIXIN_CLASS,
ParserErrorCode.SEALED_MIXIN_CLASS,
];
class ParserErrorCode extends ErrorCode {
@ -820,6 +823,12 @@ class ParserErrorCode extends ErrorCode {
correctionMessage: "Try removing the keyword 'final'.",
);
static const ParserErrorCode FINAL_MIXIN_CLASS = ParserErrorCode(
'FINAL_MIXIN_CLASS',
"A mixin class can't be declared 'final'.",
correctionMessage: "Try removing the 'final' keyword.",
);
static const ParserErrorCode FINAL_TYPEDEF = ParserErrorCode(
'FINAL_TYPEDEF',
"Typedefs can't be declared to be 'final'.",
@ -897,6 +906,12 @@ class ParserErrorCode extends ErrorCode {
"Try removing the initializer, or using a different kind of loop.",
);
static const ParserErrorCode INTERFACE_MIXIN_CLASS = ParserErrorCode(
'INTERFACE_MIXIN_CLASS',
"A mixin class can't be declared 'interface'.",
correctionMessage: "Try removing the 'interface' keyword.",
);
static const ParserErrorCode INVALID_AWAIT_IN_FOR = ParserErrorCode(
'INVALID_AWAIT_IN_FOR',
"The keyword 'await' isn't allowed for a normal 'for' statement.",
@ -1544,6 +1559,12 @@ class ParserErrorCode extends ErrorCode {
"Try making this a factory constructor, or remove the redirection.",
);
static const ParserErrorCode SEALED_MIXIN_CLASS = ParserErrorCode(
'SEALED_MIXIN_CLASS',
"A mixin class can't be declared 'sealed'.",
correctionMessage: "Try removing the 'sealed' keyword.",
);
static const ParserErrorCode SETTER_CONSTRUCTOR = ParserErrorCode(
'SETTER_CONSTRUCTOR',
"Constructors can't be a setter.",

View file

@ -742,6 +742,7 @@ const List<ErrorCode> errorCodeValues = [
ParserErrorCode.FINAL_CONSTRUCTOR,
ParserErrorCode.FINAL_ENUM,
ParserErrorCode.FINAL_METHOD,
ParserErrorCode.FINAL_MIXIN_CLASS,
ParserErrorCode.FINAL_TYPEDEF,
ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR,
ParserErrorCode.GETTER_CONSTRUCTOR,
@ -753,6 +754,7 @@ const List<ErrorCode> errorCodeValues = [
ParserErrorCode.IMPLEMENTS_BEFORE_WITH,
ParserErrorCode.IMPORT_DIRECTIVE_AFTER_PART_DIRECTIVE,
ParserErrorCode.INITIALIZED_VARIABLE_IN_FOR_EACH,
ParserErrorCode.INTERFACE_MIXIN_CLASS,
ParserErrorCode.INVALID_AWAIT_IN_FOR,
ParserErrorCode.INVALID_CODE_POINT,
ParserErrorCode.INVALID_COMMENT_REFERENCE,
@ -846,6 +848,7 @@ const List<ErrorCode> errorCodeValues = [
ParserErrorCode.RECORD_TYPE_ONE_POSITIONAL_NO_TRAILING_COMMA,
ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY,
ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR,
ParserErrorCode.SEALED_MIXIN_CLASS,
ParserErrorCode.SETTER_CONSTRUCTOR,
ParserErrorCode.SETTER_IN_FUNCTION,
ParserErrorCode.STACK_OVERFLOW,

View file

@ -284,6 +284,26 @@ ClassDeclaration
''');
}
void test_class_final_mixin() {
var parseResult = parseStringWithErrors(r'''
final mixin class A {}
''');
parseResult.assertErrors([
error(ParserErrorCode.FINAL_MIXIN_CLASS, 0, 5),
]);
var node = parseResult.findNode.classDeclaration('class A {}');
assertParsedNodeText(node, r'''
ClassDeclaration
finalKeyword: final
mixinKeyword: mixin
classKeyword: class
name: A
leftBracket: {
rightBracket: }
''');
}
void test_class_implementsClause_recordType() {
var parseResult = parseStringWithErrors(r'''
class C implements A, (int, int), B {}
@ -348,6 +368,26 @@ ClassDeclaration
''');
}
void test_class_interface_mixin() {
var parseResult = parseStringWithErrors(r'''
interface mixin class A {}
''');
parseResult.assertErrors([
error(ParserErrorCode.INTERFACE_MIXIN_CLASS, 0, 9),
]);
var node = parseResult.findNode.classDeclaration('class A {}');
assertParsedNodeText(node, r'''
ClassDeclaration
interfaceKeyword: interface
mixinKeyword: mixin
classKeyword: class
name: A
leftBracket: {
rightBracket: }
''');
}
void test_class_macro() {
var parseResult = parseStringWithErrors(r'''
macro class A {}
@ -419,6 +459,26 @@ ClassDeclaration
''');
}
void test_class_sealed_mixin() {
var parseResult = parseStringWithErrors(r'''
sealed mixin class A {}
''');
parseResult.assertErrors([
error(ParserErrorCode.SEALED_MIXIN_CLASS, 0, 6),
]);
var node = parseResult.findNode.classDeclaration('class A {}');
assertParsedNodeText(node, r'''
ClassDeclaration
sealedKeyword: sealed
mixinKeyword: mixin
classKeyword: class
name: A
leftBracket: {
rightBracket: }
''');
}
void test_class_withClause_recordType() {
var parseResult = parseStringWithErrors(r'''
class C with A, (int, int), B {}

View file

@ -6068,6 +6068,33 @@ CantUseClassAsMixin:
lib.dart:
class A {}
FinalMixinClass:
index: 142
problemMessage: "A mixin class can't be declared 'final'."
correctionMessage: "Try removing the 'final' keyword."
analyzerCode: ParserErrorCode.FINAL_MIXIN_CLASS
experiments: class-modifiers
script:
- "final mixin class C {}"
InterfaceMixinClass:
index: 143
problemMessage: "A mixin class can't be declared 'interface'."
correctionMessage: "Try removing the 'interface' keyword."
analyzerCode: ParserErrorCode.INTERFACE_MIXIN_CLASS
experiments: class-modifiers
script:
- "interface mixin class C {}"
SealedMixinClass:
index: 144
problemMessage: "A mixin class can't be declared 'sealed'."
correctionMessage: "Try removing the 'sealed' keyword."
analyzerCode: ParserErrorCode.SEALED_MIXIN_CLASS
experiments: class-modifiers,sealed-class
script:
- "sealed mixin class C {}"
BaseClassImplementedOutsideOfLibrary:
problemMessage: "The class '#name' can't be implemented outside of its library because it's a base class."
analyzerCode: BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY
@ -6458,4 +6485,4 @@ PatternAssignmentNotLocalVariable:
var global;
method(x) {
[global] = x;
}
}

View file

@ -1 +1 @@
--enable-experiment=class-modifiers
--enable-experiment=class-modifiers,sealed-class

View file

@ -0,0 +1,7 @@
// Copyright (c) 2023, 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.
final mixin class A {}
interface mixin class B {}
sealed mixin class C {}

View file

@ -0,0 +1,37 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:5:1: Error: A mixin class can't be declared 'final'.
// Try removing the 'final' keyword.
// final mixin class A {}
// ^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:6:1: Error: A mixin class can't be declared 'interface'.
// Try removing the 'interface' keyword.
// interface mixin class B {}
// ^^^^^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:7:1: Error: A mixin class can't be declared 'sealed'.
// Try removing the 'sealed' keyword.
// sealed mixin class C {}
// ^^^^^^
//
import self as self;
import "dart:core" as core;
final mixin class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
interface mixin class B extends core::Object {
synthetic constructor •() → self::B
: super core::Object::•()
;
}
abstract sealed mixin class C extends core::Object {
synthetic constructor •() → self::C
: super core::Object::•()
;
}

View file

@ -0,0 +1,37 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:5:1: Error: A mixin class can't be declared 'final'.
// Try removing the 'final' keyword.
// final mixin class A {}
// ^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:6:1: Error: A mixin class can't be declared 'interface'.
// Try removing the 'interface' keyword.
// interface mixin class B {}
// ^^^^^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:7:1: Error: A mixin class can't be declared 'sealed'.
// Try removing the 'sealed' keyword.
// sealed mixin class C {}
// ^^^^^^
//
import self as self;
import "dart:core" as core;
final mixin class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
interface mixin class B extends core::Object {
synthetic constructor •() → self::B
: super core::Object::•()
;
}
abstract sealed mixin class C extends core::Object {
synthetic constructor •() → self::C
: super core::Object::•()
;
}

View file

@ -0,0 +1,6 @@
final mixin
class A {}
interface mixin
class B {}
sealed mixin
class C {}

View file

@ -0,0 +1,37 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:5:1: Error: A mixin class can't be declared 'final'.
// Try removing the 'final' keyword.
// final mixin class A {}
// ^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:6:1: Error: A mixin class can't be declared 'interface'.
// Try removing the 'interface' keyword.
// interface mixin class B {}
// ^^^^^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:7:1: Error: A mixin class can't be declared 'sealed'.
// Try removing the 'sealed' keyword.
// sealed mixin class C {}
// ^^^^^^
//
import self as self;
import "dart:core" as core;
final mixin class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
interface mixin class B extends core::Object {
synthetic constructor •() → self::B
: super core::Object::•()
;
}
abstract sealed mixin class C extends core::Object {
synthetic constructor •() → self::C
: super core::Object::•()
;
}

View file

@ -0,0 +1,37 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:5:1: Error: A mixin class can't be declared 'final'.
// Try removing the 'final' keyword.
// final mixin class A {}
// ^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:6:1: Error: A mixin class can't be declared 'interface'.
// Try removing the 'interface' keyword.
// interface mixin class B {}
// ^^^^^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:7:1: Error: A mixin class can't be declared 'sealed'.
// Try removing the 'sealed' keyword.
// sealed mixin class C {}
// ^^^^^^
//
import self as self;
import "dart:core" as core;
final mixin class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
interface mixin class B extends core::Object {
synthetic constructor •() → self::B
: super core::Object::•()
;
}
abstract sealed mixin class C extends core::Object {
synthetic constructor •() → self::C
: super core::Object::•()
;
}

View file

@ -0,0 +1,34 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:5:1: Error: A mixin class can't be declared 'final'.
// Try removing the 'final' keyword.
// final mixin class A {}
// ^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:6:1: Error: A mixin class can't be declared 'interface'.
// Try removing the 'interface' keyword.
// interface mixin class B {}
// ^^^^^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:7:1: Error: A mixin class can't be declared 'sealed'.
// Try removing the 'sealed' keyword.
// sealed mixin class C {}
// ^^^^^^
//
import self as self;
import "dart:core" as core;
final mixin class A extends core::Object {
synthetic constructor •() → self::A
;
}
interface mixin class B extends core::Object {
synthetic constructor •() → self::B
;
}
abstract sealed mixin class C extends core::Object {
synthetic constructor •() → self::C
;
}

View file

@ -0,0 +1,37 @@
library /*isNonNullableByDefault*/;
//
// Problems in library:
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:5:1: Error: A mixin class can't be declared 'final'.
// Try removing the 'final' keyword.
// final mixin class A {}
// ^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:6:1: Error: A mixin class can't be declared 'interface'.
// Try removing the 'interface' keyword.
// interface mixin class B {}
// ^^^^^^^^^
//
// pkg/front_end/testcases/class_modifiers/mixin/mixin_class_invalid_modifiers.dart:7:1: Error: A mixin class can't be declared 'sealed'.
// Try removing the 'sealed' keyword.
// sealed mixin class C {}
// ^^^^^^
//
import self as self;
import "dart:core" as core;
final mixin class A extends core::Object {
synthetic constructor •() → self::A
: super core::Object::•()
;
}
interface mixin class B extends core::Object {
synthetic constructor •() → self::B
: super core::Object::•()
;
}
abstract sealed mixin class C extends core::Object {
synthetic constructor •() → self::C
: super core::Object::•()
;
}

View file

@ -26,6 +26,7 @@ class_modifiers/final/final_class_declaration: FormatterCrash
class_modifiers/interface/interface_class_declaration: FormatterCrash
class_modifiers/mixin/mixin_class_declaration: FormatterCrash
class_modifiers/mixin/mixin_class_generative_constructor: FormatterCrash
class_modifiers/mixin/mixin_class_invalid_modifiers: FormatterCrash
class_modifiers/mixin/mixin_class_superclass_not_object: FormatterCrash
const_functions/const_functions_const_ctor: FormatterCrash
const_functions/const_functions_const_ctor_error: FormatterCrash

View file

@ -0,0 +1,17 @@
// Copyright (c) 2023, 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.
// SharedOptions=--enable-experiment=class-modifiers
// Allow base mixin classes.
import 'package:expect/expect.dart';
base mixin class BaseMixinClass {
int foo = 0;
}
main() {
Expect.equals(0, BaseMixinClass().foo);
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2023, 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.
// SharedOptions=--enable-experiment=class-modifiers,sealed-class
// Error when applying any other modifier to mixin classes other than base.
final mixin class A {}
// [error column 1, length 5]
// [analyzer] SYNTACTIC_ERROR.FINAL_MIXIN_CLASS
// [cfe] A mixin class can't be declared 'final'.
interface mixin class B {}
// [error column 1, length 9]
// [analyzer] SYNTACTIC_ERROR.INTERFACE_MIXIN_CLASS
// [cfe] A mixin class can't be declared 'interface'.
sealed mixin class C {}
// [error column 1, length 6]
// [analyzer] SYNTACTIC_ERROR.SEALED_MIXIN_CLASS
// [cfe] A mixin class can't be declared 'sealed'.