diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart index feb40b624dd..94e7e60aba3 100644 --- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart +++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart @@ -10706,8 +10706,8 @@ const Template< const Code codeSealedClassSubtypeOutsideOfLibrary = const Code( - "SealedClassSubtypeOutsideOfLibrary", -); + "SealedClassSubtypeOutsideOfLibrary", + analyzerCodes: ["SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY"]); // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE. Message _withArgumentsSealedClassSubtypeOutsideOfLibrary(String name) { 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 f9fb5066f3d..a5f7bf3b27b 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 @@ -970,6 +970,8 @@ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD: status: hasFix CompileTimeErrorCode.RETURN_WITHOUT_VALUE: status: needsEvaluation +CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY: + status: needsEvaluation CompileTimeErrorCode.SET_ELEMENT_FROM_DEFERRED_LIBRARY: status: needsEvaluation CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE: diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart index 0d3090d1abe..9540e53c01d 100644 --- a/pkg/analyzer/lib/src/error/codes.g.dart +++ b/pkg/analyzer/lib/src/error/codes.g.dart @@ -4127,6 +4127,15 @@ class CompileTimeErrorCode extends AnalyzerErrorCode { hasPublishedDocs: true, ); + /// Parameters: + /// 0: the name of the supertype being extended, implemented, or mixed in + static const CompileTimeErrorCode SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY = + CompileTimeErrorCode( + 'SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY', + "The sealed class '{0}' can't be extended, implemented, or mixed in " + "outside of its library.", + ); + /// No parameters. static const CompileTimeErrorCode SET_ELEMENT_FROM_DEFERRED_LIBRARY = CompileTimeErrorCode( diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart index 3078befdc02..c82fd4c86e5 100644 --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart @@ -414,6 +414,7 @@ const List errorCodeValues = [ CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION, CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD, CompileTimeErrorCode.RETURN_WITHOUT_VALUE, + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, CompileTimeErrorCode.SET_ELEMENT_FROM_DEFERRED_LIBRARY, CompileTimeErrorCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE, CompileTimeErrorCode.SHARED_DEFERRED_PREFIX, diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart index f7bccc35263..37ece5f37f2 100644 --- a/pkg/analyzer/lib/src/generated/error_verifier.dart +++ b/pkg/analyzer/lib/src/generated/error_verifier.dart @@ -1405,6 +1405,8 @@ class ErrorVerifier extends RecursiveAstVisitor _checkMixinsSuperClass(withClause); _checkForMixinWithConflictingPrivateMember(withClause, superclass); _checkForConflictingGenerics(node); + _checkForSealedSupertypeOutsideOfLibrary( + node, superclass, withClause, implementsClause); if (node is ClassDeclaration) { _checkForNoDefaultSuperConstructorImplicit(node); } @@ -4204,6 +4206,49 @@ class ErrorVerifier extends RecursiveAstVisitor CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR, body); } + /// Check that if a direct supertype of a node is sealed, then it must be in + /// the same library. + /// + /// See [CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY]. + void _checkForSealedSupertypeOutsideOfLibrary( + NamedCompilationUnitMember node, + NamedType? superclass, + WithClause? withClause, + ImplementsClause? implementsClause) { + // TODO (kallentu): Distinguish between mixins and classes and make a + // separate error for both. + void reportSealedClassOutsideOfLibraryError(ClassElementImpl superclass) { + if (superclass.isSealed && superclass.library != _currentLibrary) { + errorReporter.reportErrorForNode( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, + node, + [superclass.name]); + } + } + + if (superclass != null) { + final superclassType = superclass.type; + if (superclassType is InterfaceType) { + final superclassElement = superclassType.element; + if (superclassElement is ClassElementImpl) { + reportSealedClassOutsideOfLibraryError(superclassElement); + } + } + } + // TODO (kallentu): Report errors for the [withClause]. + if (implementsClause != null) { + for (NamedType interface in implementsClause.interfaces) { + final interfaceType = interface.type; + if (interfaceType is InterfaceType) { + final interfaceElement = interfaceType.element; + if (interfaceElement is ClassElementImpl) { + reportSealedClassOutsideOfLibraryError(interfaceElement); + } + } + } + } + } + /// Verify that the elements in the given set [literal] are subtypes of the /// set's static type. /// @@ -4980,6 +5025,8 @@ class ErrorVerifier extends RecursiveAstVisitor CompileTimeErrorCode.IMPLEMENTS_REPEATED, ); _checkForConflictingGenerics(node); + _checkForSealedSupertypeOutsideOfLibrary( + node, null, null, implementsClause); } } diff --git a/pkg/analyzer/lib/src/summary2/element_builder.dart b/pkg/analyzer/lib/src/summary2/element_builder.dart index fc92973233d..7b00a9cdaa1 100644 --- a/pkg/analyzer/lib/src/summary2/element_builder.dart +++ b/pkg/analyzer/lib/src/summary2/element_builder.dart @@ -91,7 +91,10 @@ class ElementBuilder extends ThrowingAstVisitor { var element = ClassElementImpl(name, nameToken.offset); element.isAbstract = node.abstractKeyword != null; element.isMacro = node.macroKeyword != null; - element.isSealed = node.sealedKeyword != null; + if (node.sealedKeyword != null) { + element.isSealed = true; + element.isAbstract = true; + } element.metadata = _buildAnnotations(node.metadata); _setCodeRange(element, node); _setDocumentation(element, node); @@ -125,7 +128,10 @@ class ElementBuilder extends ThrowingAstVisitor { var element = ClassElementImpl(name, nameToken.offset); element.isAbstract = node.abstractKeyword != null; element.isMacro = node.macroKeyword != null; - element.isSealed = node.sealedKeyword != null; + if (node.sealedKeyword != null) { + element.isSealed = true; + element.isAbstract = true; + } element.isMixinApplication = true; element.metadata = _buildAnnotations(node.metadata); _setCodeRange(element, node); diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 60ffd6e2b2c..14c4cdd8293 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -12557,6 +12557,11 @@ CompileTimeErrorCode: return 0; } ``` + SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY: + problemMessage: "The sealed class '{0}' can't be extended, implemented, or mixed in outside of its library." + comment: |- + Parameters: + 0: the name of the supertype being extended, implemented, or mixed in SET_ELEMENT_TYPE_NOT_ASSIGNABLE: problemMessage: "The element type '{0}' can't be assigned to the set type '{1}'." hasPublishedDocs: true diff --git a/pkg/analyzer/test/src/diagnostics/sealed_class_subtype_outside_of_library_test.dart b/pkg/analyzer/test/src/diagnostics/sealed_class_subtype_outside_of_library_test.dart new file mode 100644 index 00000000000..5100b9e36bc --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/sealed_class_subtype_outside_of_library_test.dart @@ -0,0 +1,157 @@ +// Copyright (c) 2022, 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/error/codes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../dart/resolution/context_collection_resolution.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(SealedClassSubtypeOutsideOfLibraryTest); + }); +} + +@reflectiveTest +class SealedClassSubtypeOutsideOfLibraryTest extends PubPackageResolutionTest { + test_class_extends_sealed_inside() async { + await assertNoErrorsInCode(r''' +sealed class Foo {} +class Bar extends Foo {} +'''); + } + + test_class_extends_sealed_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +class Bar extends Foo {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 19, 24), + ]); + } + + test_class_extends_sealed_outside_viaTypedef_inside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +typedef FooTypedef = Foo; +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +class Bar extends FooTypedef {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 19, 31), + ]); + } + + test_class_extends_sealed_outside_viaTypedef_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +typedef FooTypedef = Foo; +class Bar extends FooTypedef {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 45, 31), + ]); + } + + test_class_extends_subtypeOfSealed_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +class Bar extends Foo {} +'''); + + await assertNoErrorsInCode(r''' +import 'foo.dart'; +class Bar2 extends Bar {} +'''); + } + + test_class_implements_sealed_inside() async { + await assertNoErrorsInCode(r''' +sealed class Foo {} +class Bar implements Foo {} +'''); + } + + test_class_implements_sealed_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +class Bar implements Foo {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 19, 27), + ]); + } + + test_class_implements_sealed_outside_viaTypedef_inside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +typedef FooTypedef = Foo; +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +class Bar implements FooTypedef {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 19, 34), + ]); + } + + test_class_implements_sealed_outside_viaTypedef_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +typedef FooTypedef = Foo; +class Bar implements FooTypedef {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 45, 34), + ]); + } + + test_class_implements_subtypeOfSealed_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +class Bar implements Foo {} +'''); + + await assertNoErrorsInCode(r''' +import 'foo.dart'; +class Bar2 implements Bar {} +'''); + } + + test_mixin_implements_sealed_outside() async { + newFile('$testPackageLibPath/foo.dart', r''' +sealed class Foo {} +'''); + + await assertErrorsInCode(r''' +import 'foo.dart'; +mixin Bar implements Foo {} +''', [ + error( + CompileTimeErrorCode.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY, 19, 27), + ]); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index dd56dff34e7..89fe1b3b910 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -687,6 +687,8 @@ import 'sdk_version_set_literal_test.dart' as sdk_version_set_literal; import 'sdk_version_ui_as_code_in_const_context_test.dart' as sdk_version_ui_as_code_in_const_context; import 'sdk_version_ui_as_code_test.dart' as sdk_version_ui_as_code; +import 'sealed_class_subtype_outside_of_library_test.dart' + as sealed_class_subtype_outside_of_library; import 'set_element_from_deferred_library_test.dart' as set_element_from_deferred_library; import 'set_element_type_not_assignable_test.dart' @@ -1268,6 +1270,7 @@ main() { sdk_version_set_literal.main(); sdk_version_ui_as_code.main(); sdk_version_ui_as_code_in_const_context.main(); + sealed_class_subtype_outside_of_library.main(); set_element_type_not_assignable.main(); shared_deferred_prefix.main(); size_annotation_dimensions.main(); diff --git a/pkg/analyzer/test/src/summary/elements_test.dart b/pkg/analyzer/test/src/summary/elements_test.dart index 8f48d745374..6734483f46e 100644 --- a/pkg/analyzer/test/src/summary/elements_test.dart +++ b/pkg/analyzer/test/src/summary/elements_test.dart @@ -7576,7 +7576,7 @@ library library definingUnit classes - sealed class C @13 + abstract sealed class C @13 constructors synthetic @-1 '''); @@ -9052,7 +9052,7 @@ mixin M {} library definingUnit classes - sealed class alias C @13 + abstract sealed class alias C @13 supertype: Object mixins M diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status index fdf1f64c333..e8dff3a891e 100644 --- a/pkg/front_end/messages.status +++ b/pkg/front_end/messages.status @@ -864,7 +864,6 @@ SdkSpecificationNotFound/analyzerCode: Fail SdkSpecificationNotFound/example: Fail SdkSummaryNotFound/analyzerCode: Fail SdkSummaryNotFound/example: Fail -SealedClassSubtypeOutsideOfLibrary/analyzerCode: Fail SealedClassSubtypeOutsideOfLibrary/part_wrapped_script: Fail # Uses imports SealedMixinSubtypeOutsideOfLibrary/analyzerCode: Fail SealedMixinSubtypeOutsideOfLibrary/part_wrapped_script: Fail # Uses imports diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml index fc7da85707a..241aedc917e 100644 --- a/pkg/front_end/messages.yaml +++ b/pkg/front_end/messages.yaml @@ -6004,6 +6004,7 @@ InheritedRestrictedMemberOfEnumImplementer: SealedClassSubtypeOutsideOfLibrary: problemMessage: "Sealed class '#name' can't be extended, implemented, or mixed in outside of its library." + analyzerCode: SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY experiments: sealed-class script: main.dart: diff --git a/tests/language/sealed_class/sealed_class_construct_error_test.dart b/tests/language/sealed_class/sealed_class_construct_error_test.dart index 7491ab8e7ab..9df3a4c8063 100644 --- a/tests/language/sealed_class/sealed_class_construct_error_test.dart +++ b/tests/language/sealed_class/sealed_class_construct_error_test.dart @@ -16,13 +16,15 @@ sealed class NotConstructableWithMixin = Object with M; main() { var error = NotConstructable(); - // ^ + // ^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.INSTANTIATE_ABSTRACT_CLASS // [cfe] The class 'NotConstructable' is abstract and can't be instantiated. var error2 = AlsoNotConstructable(); // ^^^^^^^^^^^^^^^^^^^^ // [analyzer] COMPILE_TIME_ERROR.MIXIN_INSTANTIATE // [cfe] Couldn't find constructor 'AlsoNotConstructable'. var error3 = NotConstructableWithMixin(); - // ^ + // ^^^^^^^^^^^^^^^^^^^^^^^^^ + // [analyzer] COMPILE_TIME_ERROR.INSTANTIATE_ABSTRACT_CLASS // [cfe] The class 'NotConstructableWithMixin' is abstract and can't be instantiated. } diff --git a/tests/language/sealed_class/sealed_class_extend_error_test.dart b/tests/language/sealed_class/sealed_class_extend_error_test.dart index f207ce07aec..07a4214a862 100644 --- a/tests/language/sealed_class/sealed_class_extend_error_test.dart +++ b/tests/language/sealed_class/sealed_class_extend_error_test.dart @@ -9,13 +9,15 @@ import 'sealed_class_extend_lib.dart'; abstract class OutsideA extends SealedClass {} +// [error column 1, length 46] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. class OutsideB extends SealedClass { +// [error column 1, length 326] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. @override int foo = 2; diff --git a/tests/language/sealed_class/sealed_class_implement_error_test.dart b/tests/language/sealed_class/sealed_class_implement_error_test.dart index ede66e3f68d..c8fda06a358 100644 --- a/tests/language/sealed_class/sealed_class_implement_error_test.dart +++ b/tests/language/sealed_class/sealed_class_implement_error_test.dart @@ -9,13 +9,15 @@ import "sealed_class_implement_lib.dart"; abstract class OutsideA implements SealedClass {} +// [error column 1, length 49] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. class OutsideB implements SealedClass { +// [error column 1, length 344] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. @override int nonAbstractFoo = 2; @@ -31,6 +33,7 @@ class OutsideB implements SealedClass { } mixin OutsideMixin implements SealedClass {} +// [error column 1, length 44] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. diff --git a/tests/language/sealed_class/sealed_class_typedef_error_test.dart b/tests/language/sealed_class/sealed_class_typedef_error_test.dart index 4a6414aecc4..8535f9fb51c 100644 --- a/tests/language/sealed_class/sealed_class_typedef_error_test.dart +++ b/tests/language/sealed_class/sealed_class_typedef_error_test.dart @@ -10,13 +10,15 @@ import 'sealed_class_typedef_lib.dart'; class ATypeDef extends SealedClassTypeDef {} +// [error column 1, length 44] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. class BTypeDef implements SealedClassTypeDef { +// [error column 1, length 213] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. @override int foo = 1; diff --git a/tests/language/sealed_class/sealed_class_typedef_used_outside_error_test.dart b/tests/language/sealed_class/sealed_class_typedef_used_outside_error_test.dart index bb84809fd46..7bb6707552e 100644 --- a/tests/language/sealed_class/sealed_class_typedef_used_outside_error_test.dart +++ b/tests/language/sealed_class/sealed_class_typedef_used_outside_error_test.dart @@ -13,13 +13,15 @@ import 'sealed_class_typedef_used_outside_lib.dart'; typedef ATypeDef = SealedClass; class A extends ATypeDef {} +// [error column 1, length 27] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. class B implements ATypeDef { +// [error column 1, length 184] +// [analyzer] COMPILE_TIME_ERROR.SEALED_CLASS_SUBTYPE_OUTSIDE_OF_LIBRARY // ^ -// [analyzer] unspecified // [cfe] Sealed class 'SealedClass' can't be extended, implemented, or mixed in outside of its library. int foo = 1; }