Augment. Support for abstract top-level variables and static fields.

Change-Id: Idf0ce319492c405dd06364cb6feb81e000a7c741
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/506606
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
Konstantin Shcheglov
2026-05-28 09:19:15 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 6f804b8121
commit a898adf8b7
13 changed files with 379 additions and 22 deletions
@@ -44,7 +44,7 @@
# issue created for it.
#
# Stats:
# - 78 "needsEvaluation"
# - 82 "needsEvaluation"
# - 302 "needsFix"
# - 525 "hasFix"
# - 487 "noFix"
@@ -914,6 +914,14 @@ implicit_this_reference_in_initializer:
status: hasFix
implicit_super_initializer_missing_arguments:
status: hasFix
induced_getter_not_complete_after_augmentations:
status: needsEvaluation
induced_getter_without_body:
status: needsEvaluation
induced_setter_not_complete_after_augmentations:
status: needsEvaluation
induced_setter_without_body:
status: needsEvaluation
import_internal_library:
status: hasFix
import_of_non_library:
@@ -210,6 +210,7 @@ class _UnitApiSignatureComputer {
}
void _topLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_addToken(node.abstractKeyword);
_addToken(node.augmentKeyword);
_addToken(node.externalKeyword);
_addNodeList(node.metadata);
@@ -7525,6 +7525,74 @@ inconsistentPatternVariableLogicalOr = DiagnosticWithArguments(
expectedTypes: [ExpectedType.string],
);
/// Parameters:
/// String name: the name of the variable inducing the getter
const DiagnosticWithArguments<
LocatableDiagnostic Function({required String name})
>
inducedGetterNotCompleteAfterAugmentations = DiagnosticWithArguments(
name: 'induced_getter_not_complete_after_augmentations',
problemMessage:
"The getter induced by '{0}' must have a body after all augmentations are "
"applied.",
correctionMessage:
"Try adding an initializer or providing an augmentation with a getter "
"body.",
type: DiagnosticType.COMPILE_TIME_ERROR,
uniqueName: 'induced_getter_not_complete_after_augmentations',
withArguments: _withArgumentsInducedGetterNotCompleteAfterAugmentations,
expectedTypes: [ExpectedType.string],
);
/// Parameters:
/// String name: the name of the variable inducing the getter
const DiagnosticWithArguments<
LocatableDiagnostic Function({required String name})
>
inducedGetterWithoutBody = DiagnosticWithArguments(
name: 'induced_getter_without_body',
problemMessage: "The getter induced by '{0}' must have a body.",
correctionMessage: "Try removing 'abstract' and adding an initializer.",
type: DiagnosticType.COMPILE_TIME_ERROR,
uniqueName: 'induced_getter_without_body',
withArguments: _withArgumentsInducedGetterWithoutBody,
expectedTypes: [ExpectedType.string],
);
/// Parameters:
/// String name: the name of the variable inducing the setter
const DiagnosticWithArguments<
LocatableDiagnostic Function({required String name})
>
inducedSetterNotCompleteAfterAugmentations = DiagnosticWithArguments(
name: 'induced_setter_not_complete_after_augmentations',
problemMessage:
"The setter induced by '{0}' must have a body after all augmentations are "
"applied.",
correctionMessage:
"Try adding an initializer or providing an augmentation with a setter "
"body.",
type: DiagnosticType.COMPILE_TIME_ERROR,
uniqueName: 'induced_setter_not_complete_after_augmentations',
withArguments: _withArgumentsInducedSetterNotCompleteAfterAugmentations,
expectedTypes: [ExpectedType.string],
);
/// Parameters:
/// String name: the name of the variable inducing the setter
const DiagnosticWithArguments<
LocatableDiagnostic Function({required String name})
>
inducedSetterWithoutBody = DiagnosticWithArguments(
name: 'induced_setter_without_body',
problemMessage: "The setter induced by '{0}' must have a body.",
correctionMessage: "Try removing 'abstract' and adding an initializer.",
type: DiagnosticType.COMPILE_TIME_ERROR,
uniqueName: 'induced_setter_without_body',
withArguments: _withArgumentsInducedSetterWithoutBody,
expectedTypes: [ExpectedType.string],
);
/// When "strict-inference" is enabled, collection literal types must be
/// inferred via the context type, or have type arguments.
///
@@ -19899,6 +19967,36 @@ LocatableDiagnostic _withArgumentsInconsistentPatternVariableLogicalOr({
]);
}
LocatableDiagnostic _withArgumentsInducedGetterNotCompleteAfterAugmentations({
required String name,
}) {
return LocatableDiagnosticImpl(
diag.inducedGetterNotCompleteAfterAugmentations,
[name],
);
}
LocatableDiagnostic _withArgumentsInducedGetterWithoutBody({
required String name,
}) {
return LocatableDiagnosticImpl(diag.inducedGetterWithoutBody, [name]);
}
LocatableDiagnostic _withArgumentsInducedSetterNotCompleteAfterAugmentations({
required String name,
}) {
return LocatableDiagnosticImpl(
diag.inducedSetterNotCompleteAfterAugmentations,
[name],
);
}
LocatableDiagnostic _withArgumentsInducedSetterWithoutBody({
required String name,
}) {
return LocatableDiagnosticImpl(diag.inducedSetterWithoutBody, [name]);
}
LocatableDiagnostic _withArgumentsInferenceFailureOnCollectionLiteral({
required String collection,
}) {
@@ -489,6 +489,10 @@ const List<DiagnosticCode> diagnosticCodeValues = [
diag.inconsistentInheritanceGetterAndMethod,
diag.inconsistentLanguageVersionOverride,
diag.inconsistentPatternVariableLogicalOr,
diag.inducedGetterNotCompleteAfterAugmentations,
diag.inducedGetterWithoutBody,
diag.inducedSetterNotCompleteAfterAugmentations,
diag.inducedSetterWithoutBody,
diag.inferenceFailureOnCollectionLiteral,
diag.inferenceFailureOnFunctionInvocation,
diag.inferenceFailureOnFunctionReturnType,
@@ -1056,6 +1056,16 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
}
}
if (node.isStatic && node.abstractKeyword != null) {
for (var variable in node.fields.variables) {
var declaredFragment = variable.declaredFragment! as FieldFragmentImpl;
_checkForIncompleteInducedAccessors(
nameToken: variable.name,
fragment: declaredFragment,
);
}
}
if (!node.isStatic) {
if (node.fields.isConst) {
diagnosticReporter.report(
@@ -2043,7 +2053,9 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
);
}
}
} else if (node.externalKeyword == null && !variableList.isLate) {
} else if (node.abstractKeyword == null &&
node.externalKeyword == null &&
!variableList.isLate) {
for (var variable in variableList.variables) {
if (variable.initializer == null) {
if (variableList.isFinal) {
@@ -2069,6 +2081,12 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
for (var variable in node.variables.variables) {
var fragment = variable.declaredFragment;
fragment as TopLevelVariableFragmentImpl;
if (node.abstractKeyword != null) {
_checkForIncompleteInducedAccessors(
nameToken: variable.name,
fragment: fragment,
);
}
_checkForMainFunction1(variable.name, fragment);
}
@@ -4908,6 +4926,47 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
);
}
void _checkForIncompleteInducedAccessors({
required Token nameToken,
required PropertyInducingFragmentImpl fragment,
}) {
if (!_featureSet.isEnabled(Feature.augmentations)) {
return;
}
if (fragment.isAugmentation) {
return;
}
var getter = fragment.inducedGetter;
var setter = fragment.inducedSetter;
var hasAugmentations =
(getter?.element.fragments.length ?? 0) > 1 ||
(setter?.element.fragments.length ?? 0) > 1;
if (getter != null) {
if (getter.element.fragments.none((f) => f.isCompleteDeclaration)) {
var diagnostic = hasAugmentations
? diag.inducedGetterNotCompleteAfterAugmentations
: diag.inducedGetterWithoutBody;
diagnosticReporter.report(
diagnostic.withArguments(name: nameToken.lexeme).at(nameToken),
);
}
}
if (setter != null) {
if (setter.element.fragments.none((f) => f.isCompleteDeclaration)) {
var diagnostic = hasAugmentations
? diag.inducedSetterNotCompleteAfterAugmentations
: diag.inducedSetterWithoutBody;
diagnosticReporter.report(
diagnostic.withArguments(name: nameToken.lexeme).at(nameToken),
);
}
}
}
/// Check that the given [typeReference] is not a type reference and that then
/// the [name] is reference to an instance member.
///
+32
View File
@@ -5074,6 +5074,38 @@ CompileTimeErrorCode:
problemMessage: "The function or member '#name' must have a body after all augmentations are applied."
correctionMessage: "Try adding a body to the introductory declaration, or providing an augmentation with a body."
hasPublishedDocs: false
inducedGetterWithoutBody:
type: compileTimeError
parameters:
String name: the name of the variable inducing the getter
experiment: augmentations
problemMessage: "The getter induced by '#name' must have a body."
correctionMessage: "Try removing 'abstract' and adding an initializer."
hasPublishedDocs: false
inducedSetterWithoutBody:
type: compileTimeError
parameters:
String name: the name of the variable inducing the setter
experiment: augmentations
problemMessage: "The setter induced by '#name' must have a body."
correctionMessage: "Try removing 'abstract' and adding an initializer."
hasPublishedDocs: false
inducedGetterNotCompleteAfterAugmentations:
type: compileTimeError
parameters:
String name: the name of the variable inducing the getter
experiment: augmentations
problemMessage: "The getter induced by '#name' must have a body after all augmentations are applied."
correctionMessage: "Try adding an initializer or providing an augmentation with a getter body."
hasPublishedDocs: false
inducedSetterNotCompleteAfterAugmentations:
type: compileTimeError
parameters:
String name: the name of the variable inducing the setter
experiment: augmentations
problemMessage: "The setter induced by '#name' must have a body after all augmentations are applied."
correctionMessage: "Try adding an initializer or providing an augmentation with a setter body."
hasPublishedDocs: false
factoryNotCompleteAfterAugmentations:
type: compileTimeError
parameters:
@@ -2132,6 +2132,28 @@ int foo() {
);
}
test_topLevelVariable_abstract_add() {
_assertNotSameSignature(
r'''
int foo;
''',
r'''
abstract int foo;
''',
);
}
test_topLevelVariable_abstract_remove() {
_assertNotSameSignature(
r'''
abstract int foo;
''',
r'''
int foo;
''',
);
}
test_topLevelVariable_augment_add() {
_assertNotSameSignature(
r'''
@@ -308,7 +308,6 @@ int? get foo => 0;
augment abstract final String? foo;
// ^^^
// [diag.augmentationInducedGetterReturnTypeMismatch] The getter induced by this augmentation has return type 'String?', but the getter being augmented has return type 'int?'.
// [diag.finalNotInitialized] The final variable 'foo' must be initialized.
''');
}
@@ -1225,8 +1225,6 @@ augment abstract int? foo;
int? get foo => 0;
augment abstract final int? foo;
// ^^^
// [diag.finalNotInitialized] The final variable 'foo' must be initialized.
''');
}
@@ -1239,7 +1237,6 @@ set foo(int? _) {}
augment abstract final int? foo;
// ^^^
// [diag.augmentationWithoutGetterDeclaration][context 1] This augmentation induces a getter, but no getter declaration named 'foo' exists to augment.
// [diag.finalNotInitialized] The final variable 'foo' must be initialized.
''');
}
@@ -1248,8 +1245,6 @@ augment abstract final int? foo;
final int? foo = 0;
augment abstract final int? foo;
// ^^^
// [diag.finalNotInitialized] The final variable 'foo' must be initialized.
''');
}
@@ -4354,12 +4354,9 @@ int foo = 42;
}
test_topLevelVariable_topLevelVariable_augment() async {
// TODO(augmentations): Should not have notInitializedNonNullableVariable
await resolveTestCodeWithDiagnostics(r'''
int foo = 0;
augment abstract int foo;
// ^^^
// [diag.notInitializedNonNullableVariable] The non-nullable variable 'foo' must be initialized.
''');
}
@@ -641,6 +641,90 @@ class A {
''');
}
test_class_staticField_abstract() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract int foo;
// ^^^
// [diag.inducedGetterWithoutBody] The getter induced by 'foo' must have a body.
// [diag.inducedSetterWithoutBody] The setter induced by 'foo' must have a body.
}
''');
}
test_class_staticField_abstract_completeAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract int foo;
augment static int get foo => 0;
augment static set foo(int _) {}
}
''');
}
test_class_staticField_abstract_incompleteGetterAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract int foo;
// ^^^
// [diag.inducedGetterNotCompleteAfterAugmentations] The getter induced by 'foo' must have a body after all augmentations are applied.
augment static set foo(int _) {}
}
''');
}
test_class_staticField_abstract_incompleteSetterAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract int foo;
// ^^^
// [diag.inducedSetterNotCompleteAfterAugmentations] The setter induced by 'foo' must have a body after all augmentations are applied.
augment static int get foo => 0;
}
''');
}
test_class_staticField_abstract_language305() async {
await resolveTestCodeWithDiagnostics(r'''
// @dart = 3.5
class A {
static abstract int foo;
// ^^^^^^^^
// [diag.abstractStaticField] Static fields can't be declared 'abstract'.
}
''');
}
test_class_staticField_abstractFinal() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract final int foo;
// ^^^
// [diag.inducedGetterWithoutBody] The getter induced by 'foo' must have a body.
}
''');
}
test_class_staticField_abstractFinal_completeAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract final int foo;
augment static int get foo => 0;
}
''');
}
test_class_staticField_abstractFinal_incompleteGetterAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
static abstract final int foo;
// ^^^
// [diag.inducedGetterNotCompleteAfterAugmentations] The getter induced by 'foo' must have a body after all augmentations are applied.
augment static abstract final int foo;
}
''');
}
test_class_staticGetter_hasBody_augmentation_staticField() async {
await resolveTestCodeWithDiagnostics(r'''
class A {
@@ -2319,8 +2403,6 @@ augment int foo = 1;
await resolveTestCodeWithDiagnostics(r'''
int get foo => 0;
augment abstract final int foo;
// ^^^
// [diag.finalNotInitialized] The final variable 'foo' must be initialized.
''');
}
@@ -2362,8 +2444,6 @@ augment int foo = 1;
int get foo => 0;
set foo(int _) {}
augment abstract int foo;
// ^^^
// [diag.notInitializedNonNullableVariable] The non-nullable variable 'foo' must be initialized.
''');
}
@@ -2478,6 +2558,73 @@ set foo(int _);
set foo(int _);
// ^
// [diag.missingFunctionBody] A function body must be provided.
''');
}
test_topLevel_variable_abstract() async {
await resolveTestCodeWithDiagnostics(r'''
abstract int foo;
// ^^^
// [diag.inducedGetterWithoutBody] The getter induced by 'foo' must have a body.
// [diag.inducedSetterWithoutBody] The setter induced by 'foo' must have a body.
''');
}
test_topLevel_variable_abstract_completeAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
abstract int foo;
augment int get foo => 0;
augment set foo(int _) {}
''');
}
test_topLevel_variable_abstract_incompleteGetterAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
abstract int foo;
// ^^^
// [diag.inducedGetterNotCompleteAfterAugmentations] The getter induced by 'foo' must have a body after all augmentations are applied.
augment set foo(int _) {}
''');
}
test_topLevel_variable_abstract_incompleteSetterAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
abstract int foo;
// ^^^
// [diag.inducedSetterNotCompleteAfterAugmentations] The setter induced by 'foo' must have a body after all augmentations are applied.
augment int get foo => 0;
''');
}
test_topLevel_variable_abstract_language305() async {
await resolveTestCodeWithDiagnostics(r'''
// @dart = 3.5
abstract int foo;
// [diag.extraneousModifier][column 1][length 8] Can't have modifier 'abstract' here.
''');
}
test_topLevel_variable_abstractFinal() async {
await resolveTestCodeWithDiagnostics(r'''
abstract final int foo;
// ^^^
// [diag.inducedGetterWithoutBody] The getter induced by 'foo' must have a body.
''');
}
test_topLevel_variable_abstractFinal_completeAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
abstract final int foo;
augment int get foo => 0;
''');
}
test_topLevel_variable_abstractFinal_incompleteGetterAfterAugmentations() async {
await resolveTestCodeWithDiagnostics(r'''
abstract final int foo;
// ^^^
// [diag.inducedGetterNotCompleteAfterAugmentations] The getter induced by 'foo' must have a body after all augmentations are applied.
augment abstract final int foo;
''');
}
}
@@ -378,15 +378,11 @@ part 'test.dart';
int Xx = 1;
''');
// TODO(augmentations): Should not have notInitializedNonNullableVariable
await assertDiagnostics(
r'''
await assertNoDiagnostics(r'''
part of 'a.dart';
augment abstract int Xx;
''',
[error(diag.notInitializedNonNullableVariable, 40, 2)],
);
''');
}
@FailingTest(issue: 'https://github.com/dart-lang/linter/issues/5048')
@@ -43,7 +43,6 @@ final abstract class FinalAbstract {}
// [cfe] Expected ';' after this.
// [cfe] The modifier 'abstract' should be before the modifier 'final'.
// ^^^^^
// [analyzer] COMPILE_TIME_ERROR.FINAL_NOT_INITIALIZED
// [analyzer] SYNTACTIC_ERROR.MISSING_IDENTIFIER
// [cfe] Expected an identifier, but got 'class'.
base abstract class BaseAbstract {}