[analyzer] Produce error when omitting base/final/sealed on base/final superclasses through legacy libraries.

When a post-feature library implements a pre-feature library declaration that has a final core library class as a super declaration, it should not produce a base/final/sealed transitivity error. Subclasses of a base core library class will still emit this error.

Otherwise, if base/sealed/final is omitted in a non-implement case, we want to report these errors.

Bug: https://github.com/dart-lang/sdk/issues/52315
Change-Id: Icdd4f63f69b061be5eabc7fd30b703d0358018a7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/302365
Commit-Queue: Kallen Tu <kallentu@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Kallen Tu 2023-05-10 22:05:59 +00:00 committed by Commit Queue
parent 73dea244cc
commit 9c34ff75db
6 changed files with 208 additions and 29 deletions

View file

@ -162,9 +162,14 @@ class BaseOrFinalTypeVerifier {
ClassOrMixinElementImpl element, ClassOrMixinElementImpl superElement,
{NamedType? implementsNamedType}) {
ClassOrMixinElementImpl? baseOrFinalSuperElement;
if (superElement.isBase || superElement.isFinal) {
if (superElement.isBase ||
superElement.isFinal ||
(!superElement.library.featureSet.isEnabled(Feature.class_modifiers) &&
element.library.featureSet.isEnabled(Feature.class_modifiers))) {
// The 'base' or 'final' modifier may be an induced modifier. Find the
// explicitly declared 'base' or 'final' in the hierarchy.
// In the case where the super element is in a pre-feature library, we
// need to check if there's an indirect core library super element.
baseOrFinalSuperElement = _getExplicitlyBaseOrFinalElement(superElement);
} else {
// There are no restrictions on this element's modifiers.
@ -213,11 +218,24 @@ class BaseOrFinalTypeVerifier {
if (!element.isBase && !element.isFinal && !element.isSealed) {
if (baseOrFinalSuperElement.isFinal) {
// 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.
if (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;
// In the case where the 'baseOrFinalSuperElement' is a core
// library element and we are subtyping from a super element that's
// from a pre-feature library, we want to produce a final
// transitivity error.
//
// For implements clauses with the above scenario, we avoid
// over-reporting since there will already be a
// [FinalClassImplementedOutsideOfLibrary] error.
if (superElement.library.featureSet
.isEnabled(Feature.class_modifiers) ||
!baseOrFinalSuperElement.library.isInSdk ||
implementsNamedType != null) {
return false;
}
}
_errorReporter.reportErrorForElement(
CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,

View file

@ -218,6 +218,8 @@ abstract class LinkedHashSet<E> implements Set<E> {
}
}
abstract base mixin class LinkedListEntry<E extends LinkedListEntry<E>> { }
abstract mixin class ListMixin<E> implements List<E> { }
abstract mixin class MapMixin<K, V> implements Map<K, V> { }

View file

@ -57,6 +57,27 @@ class B extends A {}
]);
}
test_class_extends_outside_viaLanguage219AndCore() async {
final a = newFile('$testPackageLibPath/a.dart', r'''
// @dart=2.19
import 'dart:collection';
abstract class A implements LinkedListEntry<Never> {}
''');
await resolveFile2(a.path);
assertNoErrorsInResult();
await assertErrorsInCode(r'''
import 'a.dart';
abstract class B extends A {}
''', [
error(CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
32, 1,
text:
"The type 'B' must be 'base', 'final' or 'sealed' because the supertype 'LinkedListEntry' is 'base'."),
]);
}
test_class_implements() async {
await assertErrorsInCode(r'''
base class A {}
@ -81,6 +102,47 @@ mixin B implements A {}
]);
}
test_class_implements_outside() async {
newFile('$testPackageLibPath/a.dart', r'''
base class A {}
''');
await assertErrorsInCode(r'''
import 'a.dart';
class B implements A {}
''', [
error(CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
23, 1,
text:
"The type 'B' must be 'base', 'final' or 'sealed' because the supertype 'A' is 'base'."),
error(CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY, 36,
1),
]);
}
test_class_implements_outside_viaLanguage219AndCore() async {
final a = newFile('$testPackageLibPath/a.dart', r'''
// @dart=2.19
import 'dart:collection';
abstract class A implements LinkedListEntry<Never> {}
''');
await resolveFile2(a.path);
assertNoErrorsInResult();
await assertErrorsInCode(r'''
import 'a.dart';
abstract class B implements A {}
''', [
error(CompileTimeErrorCode.SUBTYPE_OF_BASE_IS_NOT_BASE_FINAL_OR_SEALED,
32, 1,
text:
"The type 'B' must be 'base', 'final' or 'sealed' because the supertype 'LinkedListEntry' is 'base'."),
error(CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY, 45,
1),
]);
}
test_class_sealed_extends() async {
await assertErrorsInCode(r'''
base class A {}

View file

@ -45,6 +45,33 @@ class B extends A {}
]);
}
test_class_extends_outside_viaLanguage219AndCore() async {
final a = newFile('$testPackageLibPath/a.dart', r'''
// @dart=2.19
import 'dart:core';
class A implements MapEntry<int, int> {
int get key => 0;
int get value => 1;
}
''');
await resolveFile2(a.path);
assertNoErrorsInResult();
await assertErrorsInCode(r'''
import 'a.dart';
class B extends A {
int get key => 0;
int get value => 1;
}
''', [
error(CompileTimeErrorCode.SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED,
23, 1,
text:
"The type 'B' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'."),
]);
}
test_class_implements() async {
await assertErrorsInCode(r'''
final class A {}
@ -69,6 +96,50 @@ mixin B implements A {}
]);
}
test_class_implements_outside() async {
// No [SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED] reported outside of
// library.
newFile('$testPackageLibPath/a.dart', r'''
final class A {}
''');
await assertErrorsInCode(r'''
import 'a.dart';
class B implements A {}
''', [
error(CompileTimeErrorCode.FINAL_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY, 36,
1),
]);
}
test_class_implements_outside_viaLanguage219AndCore() async {
// No [SUBTYPE_OF_FINAL_IS_NOT_BASE_FINAL_OR_SEALED] reported outside of
// library to avoid over-reporting when we have a
// [FINAL_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY] error.
final a = newFile('$testPackageLibPath/a.dart', r'''
// @dart=2.19
import 'dart:core';
class A implements MapEntry<int, int> {
int get key => 0;
int get value => 1;
}
''');
await resolveFile2(a.path);
assertNoErrorsInResult();
await assertErrorsInCode(r'''
import 'a.dart';
class B implements A {
int get key => 0;
int get value => 1;
}
''', [
error(CompileTimeErrorCode.FINAL_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY, 36,
1),
]);
}
test_class_on() async {
await assertErrorsInCode(r'''
final class A {}

View file

@ -472,8 +472,12 @@ base mixin BaseMixinImplement implements BaseClass {}
// Implementing a legacy class that implements a core library base class.
abstract class LegacyImplement<E extends LinkedListEntry<E>>
implements LegacyImplementBaseCore<E> {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'LegacyImplement' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
implements
LegacyImplementBaseCore<E> {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.INVALID_USE_OF_TYPE_OUTSIDE_LIBRARY
// [cfe] The class 'LinkedList' can't be implemented outside of its library because it's a base class.

View file

@ -44,108 +44,130 @@ abstract base class ImplementsLegacyMixinOnFinal implements LegacyMixinOnFinal {
// Not allowed to omit base on classes with base/final superclasses.
abstract class ExtendsLegacyImplementsFinal extends LegacyImplementsFinal {
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyImplementsFinal' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
}
abstract class ExtendsLegacyImplementsFinal2 = LegacyImplementsFinal
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyImplementsFinal2' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
with
_AnyMixin;
abstract class ExtendsLegacyExtendsFinal extends LegacyExtendsFinal {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyExtendsFinal' must be 'base', 'final' or 'sealed' because the supertype 'ListQueue' is 'final'.
abstract class ExtendsLegacyExtendsFinal2 = LegacyExtendsFinal2 with _AnyMixin;
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyExtendsFinal2' must be 'base', 'final' or 'sealed' because the supertype 'ListQueue' is 'final'.
abstract class ExtendsLegacyMixesInFinal extends LegacyMixesInFinal {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyMixesInFinal' must be 'base', 'final' or 'sealed' because the supertype 'BigInt' is 'final'.
abstract class ExtendsLegacyMixesInFinal2 = LegacyMixesInFinal2 with _AnyMixin;
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyMixesInFinal2' must be 'base', 'final' or 'sealed' because the supertype 'BigInt' is 'final'.
abstract class ExtendsLegacyImplementsBase extends LegacyImplementsBase {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyImplementsBase' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
abstract class ExtendsLegacyImplementsBase2 = LegacyImplementsBase
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'ExtendsLegacyImplementsBase2' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
with
_AnyMixin;
abstract class MixesInLegacyImplementsFinal with LegacyImplementsFinal {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInLegacyImplementsFinal' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
abstract class MixesInLegacyImplementsFinal2 = Object
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInLegacyImplementsFinal2' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
with
LegacyImplementsFinal;
abstract class MixesInLegacyMixesInFinal with LegacyMixesInFinal2 {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInLegacyMixesInFinal' must be 'base', 'final' or 'sealed' because the supertype 'BigInt' is 'final'.
abstract class MixesInLegacyMixesInFinal2 = Object with LegacyMixesInFinal2;
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInLegacyMixesInFinal2' must be 'base', 'final' or 'sealed' because the supertype 'BigInt' is 'final'.
abstract class MixesInLegacyImplementsBase with LegacyImplementsBase {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInLegacyImplementsBase' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
abstract class MixesInLegacyImplementsBase2 = Object with LegacyImplementsBase;
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInLegacyImplementsBase2' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
abstract class MixesInMixinOnFinal extends LegacyImplementsFinal
// ^
// ^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinOnFinal' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
with
LegacyMixinOnFinal {}
abstract class MixesInMixinOnFinal2 = LegacyImplementsFinal
// ^
// ^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinOnFinal2' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
with
LegacyMixinOnFinal;
abstract class MixesInMixinOnBase extends LegacyMixinOnBaseSuper
// ^
// ^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinOnBase' must be 'base', 'final' or 'sealed' because the supertype 'LinkedListEntry' is 'base'.
with
LegacyMixinOnBase {}
abstract class MixesInMixinOnBase2 = LegacyMixinOnBaseSuper
// ^
// ^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinOnBase2' must be 'base', 'final' or 'sealed' because the supertype 'LinkedListEntry' is 'base'.
with
LegacyMixinOnBase;
abstract class MixesInMixinImplementsFinal with LegacyMixinImplementsFinal {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinImplementsFinal' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
abstract class MixesInMixinImplementsFinal2 = Object
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinImplementsFinal2' must be 'base', 'final' or 'sealed' because the supertype 'MapEntry' is 'final'.
with
LegacyMixinImplementsFinal;
abstract class MixesInMixinImplementsBase with LegacyMixinImplementsBase {}
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinImplementsBase' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
abstract class MixesInMixinImplementsBase2 = Object
// ^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.SUBTYPE_OF_BASE_OR_FINAL_IS_NOT_BASE_FINAL_OR_SEALED
// [cfe] The type 'MixesInMixinImplementsBase2' must be 'base', 'final' or 'sealed' because the supertype 'LinkedList' is 'base'.
with
LegacyMixinImplementsBase;