Augment. Report augmentationOfMixinApplicationClass.

Change-Id: I2649f4eb96fc232727fafa16129799ef3f207e20
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/494340
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
Konstantin Shcheglov
2026-04-21 09:57:59 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 78412eaeb8
commit 18ab2664ca
10 changed files with 179 additions and 27 deletions
@@ -241,6 +241,8 @@ augmentation_modifier_missing:
status: needsFix
augmentation_of_different_declaration_kind:
status: noFix
augmentation_of_mixin_application_class:
status: needsEvaluation
augmentation_type_parameter_bound:
status: noFix
augmentation_type_parameter_count:
@@ -943,6 +943,19 @@ augmentationOfDifferentDeclarationKind = DiagnosticWithArguments(
expectedTypes: [ExpectedType.string, ExpectedType.string],
);
/// No parameters.
const DiagnosticWithoutArguments augmentationOfMixinApplicationClass =
DiagnosticWithoutArgumentsImpl(
name: 'augmentation_of_mixin_application_class',
problemMessage: "Mixin application classes can't be augmented.",
correctionMessage:
"Try removing the 'augment' keyword, or making the target a normal "
"class.",
type: DiagnosticType.COMPILE_TIME_ERROR,
uniqueName: 'augmentation_of_mixin_application_class',
expectedTypes: [],
);
/// No parameters.
const DiagnosticWithoutArguments augmentationTypeParameterBound =
DiagnosticWithoutArgumentsImpl(
@@ -73,6 +73,7 @@ const List<DiagnosticCode> diagnosticCodeValues = [
diag.augmentationModifierExtra,
diag.augmentationModifierMissing,
diag.augmentationOfDifferentDeclarationKind,
diag.augmentationOfMixinApplicationClass,
diag.augmentationTypeParameterBound,
diag.augmentationTypeParameterCount,
diag.augmentationTypeParameterName,
@@ -2018,33 +2018,38 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
if (augmentKeyword != null) {
if (fragment.previousFragment == null) {
var element = fragment.element;
if (element.previousFragmentOfDifferentKind case var previous?) {
diagnosticReporter.report(
diag.augmentationOfDifferentDeclarationKind
.withArguments(
declarationKind: previous.element.kind.displayName,
augmentationKind: element.kind.displayName,
)
.withContextMessages([
if (previous case FragmentImpl(
libraryFragment: var libraryFragment?,
name: var name?,
nameOffset: var nameOffset?,
))
DiagnosticMessageImpl(
filePath: libraryFragment.source.fullName,
message: "The declaration being augmented.",
offset: nameOffset,
length: name.length,
url: null,
var previousFragmentOfDifferentKind =
element.previousFragmentOfDifferentKind;
switch (previousFragmentOfDifferentKind) {
case ClassFragmentImpl(isMixinApplication: true):
diagnosticReporter.report(
diag.augmentationOfMixinApplicationClass
.withContextMessages([
?previousFragmentOfDifferentKind.contextMessageAt(
"The declaration being augmented.",
),
])
.at(augmentKeyword),
);
} else {
diagnosticReporter.report(
diag.augmentationWithoutDeclaration.at(augmentKeyword),
);
])
.at(augmentKeyword),
);
case FragmentImpl previousFragment:
var previousElement = previousFragment.element;
diagnosticReporter.report(
diag.augmentationOfDifferentDeclarationKind
.withArguments(
declarationKind: previousElement.kind.displayName,
augmentationKind: element.kind.displayName,
)
.withContextMessages([
?previousFragment.contextMessageAt(
"The declaration being augmented.",
),
])
.at(augmentKeyword),
);
case null:
diagnosticReporter.report(
diag.augmentationWithoutDeclaration.at(augmentKeyword),
);
}
}
}
@@ -203,7 +203,9 @@ class ElementBuilder {
) {
libraryFragment.addClass(fragment);
if (fragment.isAugmentation && lastFragment is ClassFragmentImpl) {
if (fragment.isAugmentation &&
lastFragment is ClassFragmentImpl &&
!lastFragment.isMixinApplication) {
lastFragment.addFragment(fragment);
_linkTypeParameters(
@@ -8,6 +8,7 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/diagnostic/diagnostic_message.dart';
import 'package:meta/meta.dart';
class MockLibraryImportElement implements Element {
@@ -260,6 +261,24 @@ extension FormalParameterElementImplExtension on FormalParameterElementImpl {
}
}
extension FragmentImplExtension on FragmentImpl {
DiagnosticMessageImpl? contextMessageAt(String message) {
var libraryFragment = this.libraryFragment;
var nameOffset = this.nameOffset;
var name = this.name;
if (libraryFragment != null && nameOffset != null && name != null) {
return DiagnosticMessageImpl(
filePath: libraryFragment.source.fullName,
message: message,
offset: nameOffset,
length: name.length,
url: null,
);
}
return null;
}
}
extension GetterElementImplExtension on GetterElementImpl {
PropertyAccessorFragmentImpl get asElement {
return lastFragment;
+7
View File
@@ -1869,6 +1869,13 @@ CompileTimeErrorCode:
problemMessage: The declaration being augmented doesn't exist.
correctionMessage: Try changing the augmentation to match an existing declaration.
hasPublishedDocs: false
augmentationOfMixinApplicationClass:
type: compileTimeError
parameters: none
experiment: augmentations
problemMessage: "Mixin application classes can't be augmented."
correctionMessage: Try removing the 'augment' keyword, or making the target a normal class.
hasPublishedDocs: false
augmentedExpressionIsNotSetter:
type: compileTimeError
parameters: none
@@ -0,0 +1,36 @@
// Copyright (c) 2026, 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.
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(AugmentationOfMixinApplicationClassTest);
});
}
@reflectiveTest
class AugmentationOfMixinApplicationClassTest extends PubPackageResolutionTest {
test_class() async {
await assertErrorsInCode(
r'''
class A {}
mixin M {}
class C = A with M;
augment class C {}
''',
[
error(
diag.augmentationOfMixinApplicationClass,
42,
7,
contextMessages: [message(testFile, 28, 1)],
),
],
);
}
}
@@ -47,6 +47,8 @@ import 'augmentation_modifier_missing_test.dart'
as augmentation_modifier_missing;
import 'augmentation_of_different_declaration_kind_test.dart'
as augmentation_of_different_declaration_kind;
import 'augmentation_of_mixin_application_class_test.dart'
as augmentation_of_mixin_application_class;
import 'augmentation_type_parameter_bound_test.dart'
as augmentation_type_parameter_bound;
import 'augmentation_type_parameter_count_test.dart'
@@ -984,6 +986,7 @@ main() {
augmentation_modifier_extra.main();
augmentation_modifier_missing.main();
augmentation_of_different_declaration_kind.main();
augmentation_of_mixin_application_class.main();
augmentation_type_parameter_bound.main();
augmentation_type_parameter_count.main();
augmentation_type_parameter_name.main();
@@ -26038,6 +26038,70 @@ library
''');
}
test_augmentation_mixinApplication() async {
var library = await buildLibrary(r'''
mixin M {}
class C = Object with M;
augment class C {}
''');
checkElementText(library, r'''
library
reference: <testLibrary>
fragments
#F0 <testLibraryFragment>
element: <testLibrary>
classes
#F1 isMixinApplication class C (nameOffset:17) (firstTokenOffset:11) (offset:17)
element: <testLibrary>::@class::C::@def::0
constructors
#F2 isConst isOriginMixinApplication new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:17)
element: <testLibrary>::@class::C::@def::0::@constructor::new
typeName: C
#F3 isAugmentation class C (nameOffset:50) (firstTokenOffset:36) (offset:50)
element: <testLibrary>::@class::C::@def::1
constructors
#F4 isOriginImplicitDefault new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:50)
element: <testLibrary>::@class::C::@def::1::@constructor::new
typeName: C
mixins
#F5 mixin M (nameOffset:6) (firstTokenOffset:0) (offset:6)
element: <testLibrary>::@mixin::M
classes
isMixinApplication isSimplyBounded class C
reference: <testLibrary>::@class::C::@def::0
firstFragment: #F1
supertype: Object
mixins
M
constructors
isConst isOriginMixinApplication new
reference: <testLibrary>::@class::C::@def::0::@constructor::new
firstFragment: #F2
constantInitializers
SuperConstructorInvocation
superKeyword: super @0
argumentList: ArgumentList
leftParenthesis: ( @0
rightParenthesis: ) @0
element: dart:core::@class::Object::@constructor::new
isSimplyBounded class C
reference: <testLibrary>::@class::C::@def::1
firstFragment: #F3
previousFragmentOfDifferentKind: #F1
constructors
isOriginImplicitDefault new
reference: <testLibrary>::@class::C::@def::1::@constructor::new
firstFragment: #F4
mixins
isSimplyBounded mixin M
reference: <testLibrary>::@mixin::M
firstFragment: #F5
superclassConstraints
Object
''');
}
test_augmentationTarget() async {
newFile('$testPackageLibPath/a1.dart', r'''
part of 'test.dart';