Elements. Break cycles in InterfaceElementImpl.

Change-Id: Iaea43db437064f21dee54b4c89938830caf60f39
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/445805
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov
2025-08-19 09:06:49 -07:00
committed by Commit Queue
parent 048f059ce9
commit dce956e560
14 changed files with 386 additions and 105 deletions
@@ -64,7 +64,18 @@ class B extends A {
'superclass': 1,
'interfaces': [],
'mixins': [],
'subclasses': [1],
'subclasses': [2],
},
{
'classElement': {
'kind': 'CLASS',
'name': 'Object',
'location': anything,
'flags': 0,
},
'interfaces': [],
'mixins': [],
'subclasses': [],
},
{
'classElement': {
@@ -76,7 +87,7 @@ class B extends A {
'superclass': 0,
'interfaces': [],
'mixins': [],
'subclasses': [],
'subclasses': [0],
},
]);
}
@@ -109,7 +109,7 @@ testFineAfterLibraryAnalyzerHook;
// TODO(scheglov): Clean up the list of implicitly analyzed files.
class AnalysisDriver {
/// The version of data format, should be incremented on every format change.
static const int DATA_VERSION = 522;
static const int DATA_VERSION = 523;
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.
@@ -4600,6 +4600,10 @@ abstract class InterfaceElementImpl extends InstanceElementImpl
InterfaceTypeImpl? _thisType;
/// If not `null`, this element was part of a supertypes cycle. The cycle
/// is broken by clearing supertypes for all cycle elements.
List<InterfaceElementImpl>? interfaceCycle;
/// The cached result of [allSupertypes].
List<InterfaceTypeImpl>? _allSupertypes;
@@ -560,82 +560,71 @@ class _ClassVerifier {
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceImplements],
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceOn],
/// [CompileTimeErrorCode.recursiveInterfaceInheritanceWith].
bool _checkForRecursiveInterfaceInheritance(
InterfaceElementImpl element, [
List<InterfaceElement>? path,
]) {
path ??= <InterfaceElement>[];
// Detect error condition.
int size = path.length;
// If this is not the base case (size > 0), and the enclosing class is the
// given class element then report an error.
if (size > 0 && classElement == element) {
String className = classElement.displayName;
if (size > 1) {
// Construct a string showing the cyclic implements path:
// "A, B, C, D, A"
String separator = ", ";
StringBuffer buffer = StringBuffer();
for (int i = 0; i < size; i++) {
buffer.write(path[i].displayName);
buffer.write(separator);
}
buffer.write(element.displayName);
reporter.atElement2(
classElement,
CompileTimeErrorCode.recursiveInterfaceInheritance,
arguments: [className, buffer.toString()],
);
return true;
} else {
// RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_EXTENDS or
// RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_IMPLEMENTS or
// RECURSIVE_INTERFACE_INHERITANCE_ON or
// RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_WITH
reporter.atElement2(
classElement,
_getRecursiveErrorCode(element),
arguments: [className],
);
return true;
}
}
if (path.indexOf(element) > 0) {
bool _checkForRecursiveInterfaceInheritance(InterfaceElementImpl element) {
var cycle = element.interfaceCycle;
if (cycle == null) {
return false;
}
path.add(element);
// n-case
var supertype = element.supertype;
if (supertype != null &&
_checkForRecursiveInterfaceInheritance(supertype.element, path)) {
return true;
}
for (var type in element.mixins) {
if (_checkForRecursiveInterfaceInheritance(type.element, path)) {
if (superclass case var superclass?) {
if (superclass.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceExtends,
arguments: [element.displayName],
);
return true;
}
}
if (element is MixinElementImpl) {
for (var type in element.superclassConstraints) {
if (_checkForRecursiveInterfaceInheritance(type.element, path)) {
if (onClause case var onClause?) {
for (var typeAnnotation in onClause.superclassConstraints) {
if (typeAnnotation.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceOn,
arguments: [element.displayName],
);
return true;
}
}
}
for (var type in element.interfaces) {
if (_checkForRecursiveInterfaceInheritance(type.element, path)) {
return true;
if (withClause case var withClause?) {
for (var typeAnnotation in withClause.mixinTypes) {
if (typeAnnotation.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceWith,
arguments: [element.displayName],
);
return true;
}
}
}
path.removeAt(path.length - 1);
return false;
if (implementsClause case var implementsClause?) {
for (var typeAnnotation in implementsClause.interfaces) {
if (typeAnnotation.element == element) {
reporter.atElement2(
element,
CompileTimeErrorCode.recursiveInterfaceInheritanceImplements,
arguments: [element.displayName],
);
return true;
}
}
}
reporter.atElement2(
classElement,
CompileTimeErrorCode.recursiveInterfaceInheritance,
arguments: [
element.displayName,
cycle.map((e) => e.displayName).join(', '),
],
);
return true;
}
void _checkIllegalConcreteEnumMemberDeclaration(Token name) {
@@ -742,30 +731,6 @@ class _ClassVerifier {
return true;
}
/// Return the error code that should be used when the given class [element]
/// references itself directly.
DiagnosticCode _getRecursiveErrorCode(InterfaceElement element) {
if (element.supertype?.element == classElement) {
return CompileTimeErrorCode.recursiveInterfaceInheritanceExtends;
}
if (element is MixinElement) {
for (var type in element.superclassConstraints) {
if (type.element == classElement) {
return CompileTimeErrorCode.recursiveInterfaceInheritanceOn;
}
}
}
for (var type in element.mixins) {
if (type.element == classElement) {
return CompileTimeErrorCode.recursiveInterfaceInheritanceWith;
}
}
return CompileTimeErrorCode.recursiveInterfaceInheritanceImplements;
}
/// If [name] is not implemented in the extended concrete class, the
/// issue should be fixed there, and then [classElement] will not have it too.
bool _isNotImplementedInConcreteSuperClass(Name name) {
@@ -293,6 +293,7 @@ class LibraryReader {
element.supertype = reader._readOptionalInterfaceType();
element.mixins = reader._readInterfaceTypeList();
element.interfaces = reader._readInterfaceTypeList();
element.interfaceCycle = reader.readOptionalElementList();
}),
);
@@ -493,6 +494,7 @@ class LibraryReader {
element.supertype = reader._readOptionalInterfaceType();
element.mixins = reader._readInterfaceTypeList();
element.interfaces = reader._readInterfaceTypeList();
element.interfaceCycle = reader.readOptionalElementList();
}),
);
@@ -632,6 +634,7 @@ class LibraryReader {
reader._addTypeParameters2(element.typeParameters);
element.typeErasure = reader.readRequiredType();
element.interfaces = reader._readInterfaceTypeList();
element.interfaceCycle = reader.readOptionalElementList();
}),
);
@@ -967,6 +970,7 @@ class LibraryReader {
reader._addTypeParameters2(element.typeParameters);
element.superclassConstraints = reader._readInterfaceTypeList();
element.interfaces = reader._readInterfaceTypeList();
element.interfaceCycle = reader.readOptionalElementList();
}),
);
@@ -1537,6 +1541,10 @@ class ResolutionReader {
return _readNodeList();
}
List<T>? readOptionalElementList<T extends Element>() {
return _reader.readOptionalObject(readElementList);
}
ExpressionImpl? readOptionalExpression() {
if (_reader.readBool()) {
return _readRequiredNode() as ExpressionImpl;
@@ -160,6 +160,7 @@ class BundleWriter {
_resolutionSink.writeType(element.supertype);
_resolutionSink._writeTypeList(element.mixins);
_resolutionSink._writeTypeList(element.interfaces);
_resolutionSink.writeOptionalElementList(element.interfaceCycle);
});
});
});
@@ -275,6 +276,7 @@ class BundleWriter {
_resolutionSink.writeType(element.supertype);
_resolutionSink._writeTypeList(element.mixins);
_resolutionSink._writeTypeList(element.interfaces);
_resolutionSink.writeOptionalElementList(element.interfaceCycle);
});
});
});
@@ -369,6 +371,7 @@ class BundleWriter {
_resolutionSink.withTypeParameters(element.typeParameters, () {
_resolutionSink.writeType(element.typeErasure);
_resolutionSink._writeTypeList(element.interfaces);
_resolutionSink.writeOptionalElementList(element.interfaceCycle);
});
});
});
@@ -572,6 +575,7 @@ class BundleWriter {
_resolutionSink.withTypeParameters(element.typeParameters, () {
_resolutionSink._writeTypeList(element.superclassConstraints);
_resolutionSink._writeTypeList(element.interfaces);
_resolutionSink.writeOptionalElementList(element.interfaceCycle);
});
});
});
@@ -913,6 +917,10 @@ class ResolutionSink extends _SummaryDataWriter {
}
}
void writeOptionalElementList(List<Element>? elements) {
writeOptionalObject(elements, (it) => _writeElementList(it));
}
void writeOptionalTypeList(List<DartType>? types) {
if (types != null) {
writeBool(true);
@@ -0,0 +1,107 @@
// Copyright (c) 2025, 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:_fe_analyzer_shared/src/util/dependency_walker.dart' as graph;
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/link.dart';
/// Clears interfaces for declarations that have cycles.
void breakInterfaceCycles(Linker linker, List<AstNode> declarations) {
var walker = _ImplementsWalker();
var elements = <InterfaceElementImpl>[];
for (var declaration in declarations) {
if (declaration is DeclarationImpl) {
var element = declaration.declaredFragment!.element;
// Handled elsewhere.
if (element is ExtensionTypeElementImpl) {
continue;
}
if (element is InterfaceElementImpl) {
elements.add(element);
}
}
}
for (var element in elements) {
var node = walker.getNode(element);
walker.walk(node);
}
}
class _ImplementsNode extends graph.Node<_ImplementsNode> {
final _ImplementsWalker walker;
final InterfaceElementImpl element;
@override
bool isEvaluated = false;
_ImplementsNode(this.walker, this.element);
@override
List<_ImplementsNode> computeDependencies() {
return [
element.supertype,
...element.mixins,
...element.interfaces,
if (element case MixinElementImpl element)
...element.superclassConstraints,
].nonNulls
.map((interface) => interface.element)
.map(walker.getNode)
.toList();
}
void _evaluate() {
isEvaluated = true;
}
void _markCircular(List<InterfaceElementImpl> elements) {
isEvaluated = true;
element.interfaceCycle = elements;
switch (element) {
case ClassElementImpl element:
var typeProvider = element.library.typeProvider;
element.supertype = typeProvider.objectType;
element.mixins = [];
element.interfaces = [];
case EnumElementImpl element:
element.mixins = [];
element.interfaces = [];
case ExtensionTypeElementImpl element:
element.interfaces = [];
case MixinElementImpl element:
var typeProvider = element.library.typeProvider;
element.superclassConstraints = [typeProvider.objectType];
element.interfaces = [];
default:
throw UnimplementedError('${element.runtimeType}');
}
}
}
class _ImplementsWalker extends graph.DependencyWalker<_ImplementsNode> {
final Map<InterfaceElementImpl, _ImplementsNode> nodeMap = Map.identity();
@override
void evaluate(_ImplementsNode v) {
v._evaluate();
}
@override
void evaluateScc(List<_ImplementsNode> scc) {
var elements = scc.map((node) => node.element).toList();
for (var node in scc) {
node._markCircular(elements);
}
}
_ImplementsNode getNode(InterfaceElementImpl element) {
return nodeMap[element] ??= _ImplementsNode(this, element);
}
}
@@ -14,6 +14,7 @@ import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/summary2/default_types_builder.dart';
import 'package:analyzer/src/summary2/extension_type.dart';
import 'package:analyzer/src/summary2/interface_cycles.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/type_builder.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
@@ -94,6 +95,7 @@ class TypesBuilder {
buildExtensionTypes(_linker, nodes.declarations);
_MixinsInference(_toInferMixins).perform();
breakInterfaceCycles(_linker, nodes.declarations);
}
void _addFragmentWithClause(
@@ -1241,7 +1241,7 @@ void f() {
test_isReferencedBy_ConstructorElement_classTypeAlias() async {
await _indexTestUnit('''
class M {}
class A implements B {
class A {
A() {}
A.named() {}
}
@@ -1256,14 +1256,14 @@ void f() {
''');
var constructor = findElement2.unnamedConstructor('A');
assertElementIndexText(constructor, r'''
118 9:8 || IS_INVOKED_BY qualified
158 11:8 || IS_INVOKED_BY qualified
105 9:8 || IS_INVOKED_BY qualified
145 11:8 || IS_INVOKED_BY qualified
''');
var constructor_named = findElement2.constructor('named', of: 'A');
assertElementIndexText(constructor_named, r'''
135 10:8 |.named| IS_INVOKED_BY qualified
175 12:8 |.named| IS_INVOKED_BY qualified
122 10:8 |.named| IS_INVOKED_BY qualified
162 12:8 |.named| IS_INVOKED_BY qualified
''');
}
@@ -101,7 +101,7 @@ class X extends A {}
],
);
assertElementTypes(findElement2.class_('X').allSupertypes, ['A', 'B', 'C']);
assertElementTypes(findElement2.class_('X').allSupertypes, ['A', 'Object']);
}
test_element_typeFunction_extends() async {
@@ -170,11 +170,6 @@ main() {
[
error(CompileTimeErrorCode.recursiveInterfaceInheritance, 6, 1),
error(CompileTimeErrorCode.recursiveInterfaceInheritance, 33, 1),
error(
CompileTimeErrorCode.nonAbstractClassInheritsAbstractMemberFivePlus,
60,
1,
),
error(WarningCode.unusedLocalVariable, 150, 1),
],
);
@@ -131,6 +131,7 @@ class C implements A, B {
}
''',
[
error(CompileTimeErrorCode.redirectToInvalidReturnType, 26, 1),
error(CompileTimeErrorCode.recursiveInterfaceInheritance, 37, 1),
error(CompileTimeErrorCode.recursiveFactoryRedirect, 70, 1),
error(CompileTimeErrorCode.recursiveInterfaceInheritance, 81, 1),
@@ -4460,11 +4460,6 @@ library
firstFragment: #F7
#E3 U
firstFragment: #F8
supertype: C<U, T>
alias: <testLibrary>::@typeAlias::A
typeArguments
U
T
constructors
named
reference: <testLibrary>::@class::C::@constructor::named
@@ -6548,6 +6543,132 @@ library
''');
}
test_class_cycle_interfaces() async {
var library = await buildLibrary(r'''
class A implements B {}
class B implements A {}
''');
checkElementText(library, r'''
library
reference: <testLibrary>
fragments
#F0 <testLibraryFragment>
element: <testLibrary>
classes
#F1 class A (nameOffset:6) (firstTokenOffset:0) (offset:6)
element: <testLibrary>::@class::A
constructors
#F2 synthetic new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:6)
element: <testLibrary>::@class::A::@constructor::new
typeName: A
#F3 class B (nameOffset:30) (firstTokenOffset:24) (offset:30)
element: <testLibrary>::@class::B
constructors
#F4 synthetic new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:30)
element: <testLibrary>::@class::B::@constructor::new
typeName: B
classes
class A
reference: <testLibrary>::@class::A
firstFragment: #F1
constructors
synthetic new
reference: <testLibrary>::@class::A::@constructor::new
firstFragment: #F2
class B
reference: <testLibrary>::@class::B
firstFragment: #F3
constructors
synthetic new
reference: <testLibrary>::@class::B::@constructor::new
firstFragment: #F4
''');
}
test_class_cycle_mixins() async {
var library = await buildLibrary(r'''
class A with B {}
class B with A {}
''');
checkElementText(library, r'''
library
reference: <testLibrary>
fragments
#F0 <testLibraryFragment>
element: <testLibrary>
classes
#F1 class A (nameOffset:6) (firstTokenOffset:0) (offset:6)
element: <testLibrary>::@class::A
constructors
#F2 synthetic new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:6)
element: <testLibrary>::@class::A::@constructor::new
typeName: A
#F3 class B (nameOffset:24) (firstTokenOffset:18) (offset:24)
element: <testLibrary>::@class::B
constructors
#F4 synthetic new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:24)
element: <testLibrary>::@class::B::@constructor::new
typeName: B
classes
class A
reference: <testLibrary>::@class::A
firstFragment: #F1
constructors
synthetic new
reference: <testLibrary>::@class::A::@constructor::new
firstFragment: #F2
class B
reference: <testLibrary>::@class::B
firstFragment: #F3
constructors
synthetic new
reference: <testLibrary>::@class::B::@constructor::new
firstFragment: #F4
''');
}
test_class_cycle_supertype() async {
var library = await buildLibrary(r'''
class A extends B {}
class B extends A {}
''');
checkElementText(library, r'''
library
reference: <testLibrary>
fragments
#F0 <testLibraryFragment>
element: <testLibrary>
classes
#F1 class A (nameOffset:6) (firstTokenOffset:0) (offset:6)
element: <testLibrary>::@class::A
constructors
#F2 synthetic new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:6)
element: <testLibrary>::@class::A::@constructor::new
typeName: A
#F3 class B (nameOffset:27) (firstTokenOffset:21) (offset:27)
element: <testLibrary>::@class::B
constructors
#F4 synthetic new (nameOffset:<null>) (firstTokenOffset:<null>) (offset:27)
element: <testLibrary>::@class::B::@constructor::new
typeName: B
classes
class A
reference: <testLibrary>::@class::A
firstFragment: #F1
constructors
synthetic new
reference: <testLibrary>::@class::A::@constructor::new
firstFragment: #F2
class B
reference: <testLibrary>::@class::B
firstFragment: #F3
constructors
synthetic new
reference: <testLibrary>::@class::B::@constructor::new
firstFragment: #F4
''');
}
test_class_documented() async {
var library = await buildLibrary('''
/**
@@ -233,6 +233,66 @@ library
''');
}
test_mixin_cycle_interfaces() async {
var library = await buildLibrary(r'''
mixin A implements B {}
mixin B implements A {}
''');
checkElementText(library, r'''
library
reference: <testLibrary>
fragments
#F0 <testLibraryFragment>
element: <testLibrary>
mixins
#F1 mixin A (nameOffset:6) (firstTokenOffset:0) (offset:6)
element: <testLibrary>::@mixin::A
#F2 mixin B (nameOffset:30) (firstTokenOffset:24) (offset:30)
element: <testLibrary>::@mixin::B
mixins
mixin A
reference: <testLibrary>::@mixin::A
firstFragment: #F1
superclassConstraints
Object
mixin B
reference: <testLibrary>::@mixin::B
firstFragment: #F2
superclassConstraints
Object
''');
}
test_mixin_cycle_superclassConstraints() async {
var library = await buildLibrary(r'''
mixin A on B {}
mixin B on A {}
''');
checkElementText(library, r'''
library
reference: <testLibrary>
fragments
#F0 <testLibraryFragment>
element: <testLibrary>
mixins
#F1 mixin A (nameOffset:6) (firstTokenOffset:0) (offset:6)
element: <testLibrary>::@mixin::A
#F2 mixin B (nameOffset:22) (firstTokenOffset:16) (offset:22)
element: <testLibrary>::@mixin::B
mixins
mixin A
reference: <testLibrary>::@mixin::A
firstFragment: #F1
superclassConstraints
Object
mixin B
reference: <testLibrary>::@mixin::B
firstFragment: #F2
superclassConstraints
Object
''');
}
test_mixin_field_inferredType_final() async {
var library = await buildLibrary('''
mixin M {
@@ -124,7 +124,6 @@ void test_cycle() {
// No lint
error(CompileTimeErrorCode.recursiveInterfaceInheritance, 6, 1),
error(CompileTimeErrorCode.recursiveInterfaceInheritance, 41, 1),
error(CompileTimeErrorCode.noDefaultSuperConstructorImplicit, 41, 1),
error(CompileTimeErrorCode.argumentTypeNotAssignable, 81, 4),
],
);