blob: ae6cd9e651b1343f755473f80b0a1ffd7c5aa725 [file] [log] [blame]
// Copyright (c) 2023, 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/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';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
/// Helper for verifying that subelements of a base or final element must be
/// base, final, or sealed.
class BaseOrFinalTypeVerifier {
final LibraryElement _definingLibrary;
final ErrorReporter _errorReporter;
BaseOrFinalTypeVerifier({
required LibraryElement definingLibrary,
required ErrorReporter errorReporter,
}) : _definingLibrary = definingLibrary,
_errorReporter = errorReporter;
/// Check to ensure the subelement of a base or final element must be base,
/// 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],
/// [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 (implementsClause != null &&
_checkInterfaceSupertypes(implementsClause.interfaces, element,
areImplementedInterfaces: true)) {
return;
}
if (_checkSupertypes(element.mixins, element)) {
return;
}
if (element is MixinElementImpl &&
_checkSupertypes(element.superclassConstraints, element,
areSuperclassConstraints: true)) {
return;
}
}
/// 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,
{bool areSuperclassConstraints = false}) {
for (final supertype in supertypes) {
final supertypeElement = supertype.element;
if (supertypeElement is ClassOrMixinElementImpl) {
// Return early if an error has been reported to prevent reporting
// multiple errors on one element.
if (_reportRestrictionError(subElement, supertypeElement,
isSuperclassConstraint: areSuperclassConstraints)) {
return true;
}
}
}
return false;
}
/// Returns the nearest explicitly declared 'base' or 'final' element in the
/// element hierarchy for [element].
ClassOrMixinElementImpl? _getExplicitlyBaseOrFinalElement(
ClassOrMixinElementImpl element) {
// The current element has an explicit 'base' or 'final' modifier.
if ((element.isBase || element.isFinal) && !element.isSealed) {
return element;
}
ClassOrMixinElementImpl? baseOrFinalSuperElement;
final supertype = element.supertype;
if (supertype != null) {
baseOrFinalSuperElement ??=
_getExplicitlyBaseOrFinalElementFromSuperTypes([supertype]);
}
baseOrFinalSuperElement ??=
_getExplicitlyBaseOrFinalElementFromSuperTypes(element.interfaces);
baseOrFinalSuperElement ??=
_getExplicitlyBaseOrFinalElementFromSuperTypes(element.mixins);
if (element is MixinElementImpl) {
baseOrFinalSuperElement ??=
_getExplicitlyBaseOrFinalElementFromSuperTypes(
element.superclassConstraints);
}
return baseOrFinalSuperElement;
}
/// Returns the first explicitly declared 'base' or 'final' element found in
/// the class hierarchies of a supertype in [supertypes], or `null` if there
/// is none.
ClassOrMixinElementImpl? _getExplicitlyBaseOrFinalElementFromSuperTypes(
List<InterfaceType> supertypes) {
ClassOrMixinElementImpl? baseOrFinalElement;
for (final supertype in supertypes) {
final supertypeElement = supertype.element;
if (supertypeElement is ClassOrMixinElementImpl) {
baseOrFinalElement = _getExplicitlyBaseOrFinalElement(supertypeElement);
if (baseOrFinalElement != null) {
return baseOrFinalElement;
}
}
}
return baseOrFinalElement;
}
/// Checks whether a `final`, `base` or `interface` modifier can be ignored.
///
/// Checks whether a subclass in the current library
/// can ignore a class modifier of a declaration in [superLibrary].
///
/// Only true if the supertype library is a platform library, and
/// either the current library is also a platform library,
/// or the current library has a language version which predates
/// class modifiers.
bool _mayIgnoreClassModifiers(LibraryElement superLibrary) {
// Only modifiers in platform libraries can be ignored.
if (!superLibrary.isInSdk) return false;
// Other platform libraries can ignore modifiers.
if (_definingLibrary.isInSdk) return true;
// Libraries predating class modifiers can ignore platform modifiers.
return !_definingLibrary.featureSet.isEnabled(Feature.class_modifiers);
}
/// Returns true if a element modifier restriction error has been reported.
///
/// Reports an error based on the modifier of the [superElement].
bool _reportRestrictionError(
ClassOrMixinElementImpl element, ClassOrMixinElementImpl superElement,
{bool isSuperclassConstraint = false, NamedType? implementsNamedType}) {
ClassOrMixinElementImpl? baseOrFinalSuperElement;
if (superElement.isBase || superElement.isFinal) {
// The 'base' or 'final' modifier may be an induced modifier. Find the
// explicitly declared 'base' or 'final' in the hierarchy.
baseOrFinalSuperElement = _getExplicitlyBaseOrFinalElement(superElement);
} else {
// There are no restrictions on this element's modifiers.
return false;
}
if (element.library != _definingLibrary) {
// Only report errors on elements within the current library.
return false;
}
if (baseOrFinalSuperElement != null &&
!_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
// an induced modifier of the direct super element.
final contextMessage = <DiagnosticMessage>[
DiagnosticMessageImpl(
filePath: baseOrFinalSuperElement.source.fullName,
length: baseOrFinalSuperElement.nameLength,
message: "The type '${superElement.displayName}' is a subtype of "
"'${baseOrFinalSuperElement.displayName}', and "
"'${baseOrFinalSuperElement.displayName}' is defined here.",
offset: baseOrFinalSuperElement.nameOffset,
url: null,
)
];
// 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;
}
}
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;
}
}
}
return false;
}
}