[analyzer] Report an error when implementing a base class through a sealed class.
Bug: https://github.com/dart-lang/sdk/issues/51810
Change-Id: I478eff8484fa5d3bddfb9cf1c744e6d65a72f6c1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290400
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/analyzer/lib/src/error/base_or_final_type_verifier.dart b/pkg/analyzer/lib/src/error/base_or_final_type_verifier.dart
index ae00190..ae6cd9e 100644
--- a/pkg/analyzer/lib/src/error/base_or_final_type_verifier.dart
+++ b/pkg/analyzer/lib/src/error/base_or_final_type_verifier.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
@@ -24,16 +25,21 @@
_errorReporter = errorReporter;
/// Check to ensure the subelement of a base or final element must be base,
- /// final, or sealed. Otherwise, an error is reported on that element.
+ /// final, or sealed and that base elements are not implemented outside of its
+ /// library. Otherwise, an error is reported on that element.
///
/// See [CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED],
- /// [CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED]
- void checkElement(ClassOrMixinElementImpl element) {
+ /// [CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED],
+ /// [CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY].
+ void checkElement(
+ ClassOrMixinElementImpl element, ImplementsClause? implementsClause) {
final supertype = element.supertype;
if (supertype != null && _checkSupertypes([supertype], element)) {
return;
}
- if (_checkSupertypes(element.interfaces, element)) {
+ if (implementsClause != null &&
+ _checkInterfaceSupertypes(implementsClause.interfaces, element,
+ areImplementedInterfaces: true)) {
return;
}
if (_checkSupertypes(element.mixins, element)) {
@@ -47,6 +53,28 @@
}
/// Returns true if a 'base' or 'final' subtype modifier error is reported for
+ /// an interface in [interfaces].
+ bool _checkInterfaceSupertypes(
+ List<NamedType> interfaces, ClassOrMixinElementImpl subElement,
+ {bool areImplementedInterfaces = false}) {
+ for (NamedType interface in interfaces) {
+ final interfaceType = interface.type;
+ if (interfaceType is InterfaceType) {
+ final interfaceElement = interfaceType.element;
+ if (interfaceElement is ClassOrMixinElementImpl) {
+ // Return early if an error has been reported to prevent reporting
+ // multiple errors on one element.
+ if (_reportRestrictionError(subElement, interfaceElement,
+ implementsNamedType: interface)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /// Returns true if a 'base' or 'final' subtype modifier error is reported for
/// a supertype in [supertypes].
bool _checkSupertypes(
List<InterfaceType> supertypes, ClassOrMixinElementImpl subElement,
@@ -135,7 +163,7 @@
/// Reports an error based on the modifier of the [superElement].
bool _reportRestrictionError(
ClassOrMixinElementImpl element, ClassOrMixinElementImpl superElement,
- {bool isSuperclassConstraint = false}) {
+ {bool isSuperclassConstraint = false, NamedType? implementsNamedType}) {
ClassOrMixinElementImpl? baseOrFinalSuperElement;
if (superElement.isBase || superElement.isFinal) {
// The 'base' or 'final' modifier may be an induced modifier. Find the
@@ -151,9 +179,6 @@
return false;
}
if (baseOrFinalSuperElement != null &&
- !element.isBase &&
- !element.isFinal &&
- !element.isSealed &&
!_mayIgnoreClassModifiers(baseOrFinalSuperElement.library)) {
// The context message links to the explicitly declared 'base' or 'final'
// super element and is only added onto the error if 'base' or 'final' is
@@ -170,27 +195,44 @@
)
];
- if (baseOrFinalSuperElement.isFinal) {
- if (!isSuperclassConstraint &&
- baseOrFinalSuperElement.library != element.library) {
- // If you can't extend, implement or mix in a final element outside of
- // its library anyways, it's not helpful to report a subelement
- // modifier error.
- return false;
+ // It's an error to implement a class if it has a supertype from a
+ // different library which is marked base.
+ if (implementsNamedType != null &&
+ superElement.isSealed &&
+ baseOrFinalSuperElement.library != element.library) {
+ if (baseOrFinalSuperElement.isBase) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
+ implementsNamedType,
+ [baseOrFinalSuperElement.displayName],
+ contextMessage);
+ return true;
}
- _errorReporter.reportErrorForElement(
- CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
- element,
- [element.displayName, baseOrFinalSuperElement.displayName],
- superElement.isSealed ? contextMessage : null);
- return true;
- } else if (baseOrFinalSuperElement.isBase) {
- _errorReporter.reportErrorForElement(
- CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
- element,
- [element.displayName, baseOrFinalSuperElement.displayName],
- superElement.isSealed ? contextMessage : null);
- return true;
+ }
+
+ if (!element.isBase && !element.isFinal && !element.isSealed) {
+ if (baseOrFinalSuperElement.isFinal) {
+ if (!isSuperclassConstraint &&
+ baseOrFinalSuperElement.library != element.library) {
+ // If you can't extend, implement or mix in a final element outside of
+ // its library anyways, it's not helpful to report a subelement
+ // modifier error.
+ return false;
+ }
+ _errorReporter.reportErrorForElement(
+ CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
+ element,
+ [element.displayName, baseOrFinalSuperElement.displayName],
+ superElement.isSealed ? contextMessage : null);
+ return true;
+ } else if (baseOrFinalSuperElement.isBase) {
+ _errorReporter.reportErrorForElement(
+ CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
+ element,
+ [element.displayName, baseOrFinalSuperElement.displayName],
+ superElement.isSealed ? contextMessage : null);
+ return true;
+ }
}
}
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 680039c..4f1ffad 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -2059,8 +2059,8 @@
enclosingClass = outerType;
}
- baseOrFinalTypeVerifier
- .checkElement(node.declaredElement as ClassOrMixinElementImpl);
+ baseOrFinalTypeVerifier.checkElement(
+ node.declaredElement as ClassOrMixinElementImpl, node.implementsClause);
}
@override
@@ -2068,8 +2068,8 @@
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitClassTypeAlias(node);
- baseOrFinalTypeVerifier
- .checkElement(node.declaredElement as ClassOrMixinElementImpl);
+ baseOrFinalTypeVerifier.checkElement(
+ node.declaredElement as ClassOrMixinElementImpl, node.implementsClause);
}
@override
@@ -2989,8 +2989,8 @@
enclosingClass = outerType;
}
- baseOrFinalTypeVerifier
- .checkElement(node.declaredElement as ClassOrMixinElementImpl);
+ baseOrFinalTypeVerifier.checkElement(
+ node.declaredElement as ClassOrMixinElementImpl, node.implementsClause);
}
@override
diff --git a/pkg/analyzer/test/src/diagnostics/base_class_implemented_outside_of_library_test.dart b/pkg/analyzer/test/src/diagnostics/base_class_implemented_outside_of_library_test.dart
index 615b25b..4dbd49d 100644
--- a/pkg/analyzer/test/src/diagnostics/base_class_implemented_outside_of_library_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/base_class_implemented_outside_of_library_test.dart
@@ -5,6 +5,7 @@
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import '../../generated/test_support.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
@@ -37,6 +38,58 @@
]);
}
+ test_class_outside_sealed() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+base class A {}
+''');
+
+ await assertErrorsInCode(r'''
+import 'a.dart';
+sealed class B extends A {}
+base class C implements B {}
+''', [
+ error(
+ CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
+ 69,
+ 1,
+ text:
+ "The class 'A' can't be implemented outside of its library because it's a base class.",
+ contextMessages: [
+ ExpectedContextMessage(a.path, 11, 1,
+ text:
+ "The type 'B' is a subtype of 'A', and 'A' is defined here.")
+ ],
+ ),
+ ]);
+ }
+
+ test_class_outside_sealed_noBase() async {
+ // Instead of emitting [SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED], we
+ // tell the user that they can't implement an indirect base supertype.
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+base class A {}
+''');
+
+ await assertErrorsInCode(r'''
+import 'a.dart';
+sealed class B extends A {}
+class C implements B {}
+''', [
+ error(
+ CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
+ 64,
+ 1,
+ text:
+ "The class 'A' can't be implemented outside of its library because it's a base class.",
+ contextMessages: [
+ ExpectedContextMessage(a.path, 11, 1,
+ text:
+ "The type 'B' is a subtype of 'A', and 'A' is defined here.")
+ ],
+ ),
+ ]);
+ }
+
test_class_outside_viaTypedef_inside() async {
newFile('$testPackageLibPath/foo.dart', r'''
base class Foo {}
@@ -67,6 +120,41 @@
]);
}
+ test_classTypeAlias_inside() async {
+ await assertNoErrorsInCode(r'''
+base class A {}
+sealed class B extends A {}
+mixin M {}
+base class C = Object with M implements B;
+''');
+ }
+
+ test_classTypeAlias_outside() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+base class A {}
+''');
+
+ await assertErrorsInCode(r'''
+import 'a.dart';
+sealed class B extends A {}
+mixin M {}
+base class C = Object with M implements B;
+''', [
+ error(
+ CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
+ 96,
+ 1,
+ text:
+ "The class 'A' can't be implemented outside of its library because it's a base class.",
+ contextMessages: [
+ ExpectedContextMessage(a.path, 11, 1,
+ text:
+ "The type 'B' is a subtype of 'A', and 'A' is defined here.")
+ ],
+ ),
+ ]);
+ }
+
test_enum_inside() async {
await assertNoErrorsInCode(r'''
base class Foo {}
diff --git a/tests/language/class_modifiers/base_transitivity/base_class_different_library_error_test.dart b/tests/language/class_modifiers/base_transitivity/base_class_different_library_error_test.dart
index 9360566..f3ef842 100644
--- a/tests/language/class_modifiers/base_transitivity/base_class_different_library_error_test.dart
+++ b/tests/language/class_modifiers/base_transitivity/base_class_different_library_error_test.dart
@@ -40,48 +40,52 @@
// Implementing through a sealed class.
class SimpleSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
+// ^
// [cfe] The type 'SimpleSealedExtendImplement' must be 'base', 'final' or 'sealed' because the supertype 'BaseClass' is 'base'.
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
base class BaseSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] unspecified
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
// [cfe] unspecified
interface class InterfaceSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
+// ^
// [cfe] The type 'InterfaceSealedExtendImplement' must be 'base', 'final' or 'sealed' because the supertype 'BaseClass' is 'base'.
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
final class FinalSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] unspecified
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
// [cfe] unspecified
sealed class SealedSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] unspecified
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
// [cfe] unspecified
mixin class MixinClassSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
+// ^
// [cfe] The type 'MixinClassSealedExtendImplement' must be 'base', 'final' or 'sealed' because the supertype 'BaseClass' is 'base'.
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
base mixin class BaseMixinClassSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] unspecified
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
// [cfe] unspecified
mixin MixinSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
+// ^
// [cfe] The type 'MixinSealedExtendImplement' must be 'base', 'final' or 'sealed' because the supertype 'BaseClass' is 'base'.
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
base mixin BaseMixinSealedExtendImplement implements SealedExtend {}
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-// [analyzer] unspecified
+// ^^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
// [cfe] unspecified
// Using a sealed class as an `on` type