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 11ab49ff566..a5575cf1607 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 @@ -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: diff --git a/pkg/analyzer/lib/src/diagnostic/diagnostic.g.dart b/pkg/analyzer/lib/src/diagnostic/diagnostic.g.dart index 4c92ffae247..be28181bfde 100644 --- a/pkg/analyzer/lib/src/diagnostic/diagnostic.g.dart +++ b/pkg/analyzer/lib/src/diagnostic/diagnostic.g.dart @@ -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( diff --git a/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart b/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart index e09b8ffbd23..1dc63041c82 100644 --- a/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart +++ b/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart @@ -73,6 +73,7 @@ const List diagnosticCodeValues = [ diag.augmentationModifierExtra, diag.augmentationModifierMissing, diag.augmentationOfDifferentDeclarationKind, + diag.augmentationOfMixinApplicationClass, diag.augmentationTypeParameterBound, diag.augmentationTypeParameterCount, diag.augmentationTypeParameterName, diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart index 3c445a442f6..d3b4f7b3d5b 100644 --- a/pkg/analyzer/lib/src/generated/error_verifier.dart +++ b/pkg/analyzer/lib/src/generated/error_verifier.dart @@ -2018,33 +2018,38 @@ class ErrorVerifier extends RecursiveAstVisitor 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), + ); } } } diff --git a/pkg/analyzer/lib/src/summary2/element_builder.dart b/pkg/analyzer/lib/src/summary2/element_builder.dart index 5cc3d3d9ea7..00f8f2fd6a8 100644 --- a/pkg/analyzer/lib/src/summary2/element_builder.dart +++ b/pkg/analyzer/lib/src/summary2/element_builder.dart @@ -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( diff --git a/pkg/analyzer/lib/src/utilities/extensions/element.dart b/pkg/analyzer/lib/src/utilities/extensions/element.dart index 578a650efff..ef5b0c76125 100644 --- a/pkg/analyzer/lib/src/utilities/extensions/element.dart +++ b/pkg/analyzer/lib/src/utilities/extensions/element.dart @@ -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; diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 357d4d0a95e..54c80be9230 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -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 diff --git a/pkg/analyzer/test/src/diagnostics/augmentation_of_mixin_application_class_test.dart b/pkg/analyzer/test/src/diagnostics/augmentation_of_mixin_application_class_test.dart new file mode 100644 index 00000000000..7136b9d1369 --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/augmentation_of_mixin_application_class_test.dart @@ -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)], + ), + ], + ); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index f41120da584..b7f80580bdb 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -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(); diff --git a/pkg/analyzer/test/src/summary/elements/class_test.dart b/pkg/analyzer/test/src/summary/elements/class_test.dart index 12f741a332a..3a9c41bb3be 100644 --- a/pkg/analyzer/test/src/summary/elements/class_test.dart +++ b/pkg/analyzer/test/src/summary/elements/class_test.dart @@ -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: + fragments + #F0 + element: + classes + #F1 isMixinApplication class C (nameOffset:17) (firstTokenOffset:11) (offset:17) + element: ::@class::C::@def::0 + constructors + #F2 isConst isOriginMixinApplication new (nameOffset:) (firstTokenOffset:) (offset:17) + element: ::@class::C::@def::0::@constructor::new + typeName: C + #F3 isAugmentation class C (nameOffset:50) (firstTokenOffset:36) (offset:50) + element: ::@class::C::@def::1 + constructors + #F4 isOriginImplicitDefault new (nameOffset:) (firstTokenOffset:) (offset:50) + element: ::@class::C::@def::1::@constructor::new + typeName: C + mixins + #F5 mixin M (nameOffset:6) (firstTokenOffset:0) (offset:6) + element: ::@mixin::M + classes + isMixinApplication isSimplyBounded class C + reference: ::@class::C::@def::0 + firstFragment: #F1 + supertype: Object + mixins + M + constructors + isConst isOriginMixinApplication new + reference: ::@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: ::@class::C::@def::1 + firstFragment: #F3 + previousFragmentOfDifferentKind: #F1 + constructors + isOriginImplicitDefault new + reference: ::@class::C::@def::1::@constructor::new + firstFragment: #F4 + mixins + isSimplyBounded mixin M + reference: ::@mixin::M + firstFragment: #F5 + superclassConstraints + Object +'''); + } + test_augmentationTarget() async { newFile('$testPackageLibPath/a1.dart', r''' part of 'test.dart';