Fine. Use tokens and elements for constant expressions.

Extends `ManifestElement` to support not only top-level elements, but also members.

Change-Id: Ia714c4f23113883bac6d6b44ede3c1dfecbbb80d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419989
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov
2025-04-03 12:42:47 -07:00
committed by Commit Queue
parent 8bfe452ffd
commit 65a5ed2c09
5 changed files with 534 additions and 225 deletions
+155 -167
View File
@@ -2,40 +2,136 @@
// 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 'dart:typed_data';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/fine/manifest_context.dart';
import 'package:analyzer/src/summary2/data_reader.dart';
import 'package:analyzer/src/summary2/data_writer.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:collection/collection.dart';
sealed class ManifestNode {
bool match(MatchContext context, AstNode node);
/// Enough information to decide if the node is the same.
///
/// We don't store ASTs, instead we rely on the fact that the same tokens
/// are parsed into the same AST (when the same language features, which is
/// ensured outside).
///
/// In addition we record all referenced elements.
class ManifestNode {
/// The concatenated lexemes of all tokens.
final String tokenBuffer;
void write(BufferedSink sink);
/// The length of each token in [tokenBuffer].
final Uint32List tokenLengthList;
static ManifestNode encode(EncodeContext context, AstNode node) {
switch (node) {
case Annotation():
return ManifestNodeAnnotation.encode(context, node);
case IntegerLiteral():
return ManifestNodeIntegerLiteral.encode(node);
case SimpleIdentifier():
return ManifestNodeSimpleIdentifier.encode(context, node);
default:
throw UnimplementedError('(${node.runtimeType}) $node');
/// All unique elements referenced by this node.
final List<ManifestElement> elements;
/// For each property in the AST structure summarized by this manifest that
/// might point to an element, `0` if the element pointer is `null`; otherwise
/// one plus the index of the associated manifest element in [elements].
///
/// The order of this list reflects the AST structure, according to the
/// behavior of [_ElementCollector].
final Uint32List elementIndexList;
factory ManifestNode.encode(EncodeContext context, AstNode node) {
var buffer = StringBuffer();
var lengthList = <int>[];
var token = node.beginToken;
while (true) {
buffer.write(token.lexeme);
lengthList.add(token.lexeme.length);
if (token == node.endToken) {
break;
}
token = token.next ?? (throw StateError('endToken not found'));
}
var collector = _ElementCollector();
node.accept(collector);
return ManifestNode._(
tokenBuffer: buffer.toString(),
tokenLengthList: Uint32List.fromList(lengthList),
elements: collector.map.keys
.map((element) => ManifestElement.encode(context, element))
.toFixedList(),
elementIndexList: Uint32List.fromList(collector.elementIndexList),
);
}
static ManifestNode read(SummaryDataReader reader) {
var kind = reader.readEnum(_ManifestNodeKind.values);
switch (kind) {
case _ManifestNodeKind.annotation:
return ManifestNodeAnnotation.read(reader);
case _ManifestNodeKind.integerLiteral:
return ManifestNodeIntegerLiteral.read(reader);
case _ManifestNodeKind.simpleIdentifier:
return ManifestNodeSimpleIdentifier.read(reader);
factory ManifestNode.read(SummaryDataReader reader) {
return ManifestNode._(
tokenBuffer: reader.readStringUtf8(),
tokenLengthList: reader.readUInt30List(),
elements: ManifestElement.readList(reader),
elementIndexList: reader.readUInt30List(),
);
}
ManifestNode._({
required this.tokenBuffer,
required this.tokenLengthList,
required this.elements,
required this.elementIndexList,
});
bool match(MatchContext context, AstNode node) {
var tokenIndex = 0;
var tokenOffset = 0;
var token = node.beginToken;
while (true) {
var tokenLength = token.lexeme.length;
if (tokenLengthList[tokenIndex++] != tokenLength) {
return false;
}
if (!tokenBuffer.startsWith(token.lexeme, tokenOffset)) {
return false;
}
tokenOffset += tokenLength;
if (token == node.endToken) {
break;
}
token = token.next ?? (throw StateError('endToken not found'));
}
var collector = _ElementCollector();
node.accept(collector);
// Must reference the same elements.
if (collector.map.length != elements.length) {
return false;
}
for (var (index, element) in collector.map.keys.indexed) {
if (!elements[index].match(context, element)) {
return false;
}
}
// Must reference elements in the same order.
if (!const ListEquality<int>().equals(
collector.elementIndexList,
elementIndexList,
)) {
return false;
}
return true;
}
void write(BufferedSink sink) {
sink.writeStringUtf8(tokenBuffer);
sink.writeUint30List(tokenLengthList);
sink.writeList(elements, (e) => e.write(sink));
sink.writeUint30List(elementIndexList);
}
static ManifestNode? readOptional(SummaryDataReader reader) {
@@ -43,162 +139,54 @@ sealed class ManifestNode {
}
}
class ManifestNodeAnnotation extends ManifestNode {
final ManifestNodeSimpleIdentifier name;
class _ElementCollector extends ThrowingAstVisitor<void> {
static const int _nullIndex = 0;
ManifestNodeAnnotation({
required this.name,
});
final Map<Element2, int> map = Map.identity();
final List<int> elementIndexList = [];
factory ManifestNodeAnnotation.encode(
EncodeContext context,
Annotation node,
) {
if (node.name case SimpleIdentifier identifier) {
return ManifestNodeAnnotation(
name: ManifestNodeSimpleIdentifier.encode(context, identifier),
);
@override
void visitAnnotation(Annotation node) {
// TODO(scheglov): implement visitAnnotation
}
@override
void visitBinaryExpression(BinaryExpression node) {
node.leftOperand.accept(this);
_addElement(node.element);
node.rightOperand.accept(this);
}
@override
void visitIntegerLiteral(IntegerLiteral node) {}
@override
void visitParenthesizedExpression(ParenthesizedExpression node) {
node.visitChildren(this);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
_addElement(node.element);
}
void _addElement(Element2? element) {
if (element == null) {
elementIndexList.add(_nullIndex);
} else {
throw UnimplementedError('(${node.runtimeType}) $node');
var index = map[element] ??= 1 + map.length;
elementIndexList.add(index);
}
}
factory ManifestNodeAnnotation.read(SummaryDataReader reader) {
return ManifestNodeAnnotation(
name: ManifestNodeSimpleIdentifier.read(reader),
);
}
@override
bool match(MatchContext context, AstNode node) {
if (node is! Annotation) {
return false;
}
if (!name.match(context, node.name)) {
return false;
}
return true;
}
@override
void write(BufferedSink sink) {
sink.writeEnum(_ManifestNodeKind.annotation);
name.writeNoTag(sink);
}
}
class ManifestNodeIntegerLiteral extends ManifestNode {
final int? value;
ManifestNodeIntegerLiteral({
required this.value,
});
factory ManifestNodeIntegerLiteral.encode(IntegerLiteral node) {
return ManifestNodeIntegerLiteral(
value: node.value,
);
}
factory ManifestNodeIntegerLiteral.read(SummaryDataReader reader) {
return ManifestNodeIntegerLiteral(
value: reader.readOptionalInt64(),
);
}
@override
bool match(MatchContext context, AstNode node) {
return node is IntegerLiteral && node.value == value;
}
@override
void write(BufferedSink sink) {
sink.writeEnum(_ManifestNodeKind.integerLiteral);
sink.writeOptionalInt64(value);
}
}
class ManifestNodeSimpleIdentifier extends ManifestNode {
final String name;
final ManifestElement? element;
ManifestNodeSimpleIdentifier({
required this.name,
required this.element,
});
factory ManifestNodeSimpleIdentifier.encode(
EncodeContext context,
SimpleIdentifier node,
) {
var element = node.element;
return ManifestNodeSimpleIdentifier(
name: node.name,
element:
element != null ? ManifestElement.encode(context, element) : null,
);
}
factory ManifestNodeSimpleIdentifier.read(SummaryDataReader reader) {
return ManifestNodeSimpleIdentifier(
name: reader.readStringUtf8(),
element: reader.readOptionalObject(
() => ManifestElement.read(reader),
),
);
}
@override
bool match(MatchContext context, AstNode node) {
if (node is! SimpleIdentifier) {
return false;
}
if (node.name != name) {
return false;
}
var element = this.element;
var nodeElement = node.element;
if (element == null && nodeElement == null) {
} else if (element == null || nodeElement == null) {
return false;
} else if (!element.match(context, nodeElement)) {
return false;
}
return true;
}
@override
void write(BufferedSink sink) {
sink.writeEnum(_ManifestNodeKind.simpleIdentifier);
writeNoTag(sink);
}
void writeNoTag(BufferedSink sink) {
sink.writeStringUtf8(name);
sink.writeOptionalObject(element, (it) => it.write(sink));
}
}
enum _ManifestNodeKind {
annotation,
integerLiteral,
simpleIdentifier,
}
extension ManifestNodeOrNullExtension on ManifestNode? {
bool match(MatchContext context, AstNode? node) {
var self = this;
if (self == null && node == null) {
return true;
} else if (self == null || node == null) {
return false;
} else {
if (self != null && node != null) {
return self.match(context, node);
} else {
return self == null && node == null;
}
}
+87 -28
View File
@@ -5,6 +5,7 @@
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/src/fine/lookup_name.dart';
import 'package:analyzer/src/fine/manifest_id.dart';
import 'package:analyzer/src/fine/manifest_item.dart';
import 'package:analyzer/src/fine/manifest_type.dart';
import 'package:analyzer/src/summary2/data_reader.dart';
import 'package:analyzer/src/summary2/data_writer.dart';
@@ -68,59 +69,78 @@ final class ManifestElement {
/// The URI of the library that declares the element.
final Uri libraryUri;
/// The name of the element.
final String name;
/// The top-level element name.
final String topLevelName;
/// The member name, e.g. a method name.
final String? memberName;
/// The id of the element, if not from the same bundle.
final ManifestItemId? id;
ManifestElement({
required this.libraryUri,
required this.name,
required this.topLevelName,
required this.memberName,
required this.id,
});
factory ManifestElement.read(SummaryDataReader reader) {
return ManifestElement(
libraryUri: reader.readUri(),
name: reader.readStringUtf8(),
topLevelName: reader.readStringUtf8(),
memberName: reader.readOptionalStringUtf8(),
id: reader.readOptionalObject(() => ManifestItemId.read(reader)),
);
}
@override
int get hashCode => Object.hash(libraryUri, name);
int get hashCode => Object.hash(libraryUri, topLevelName, memberName);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ManifestElement &&
other.libraryUri == libraryUri &&
other.name == name;
other.topLevelName == topLevelName &&
other.memberName == memberName;
}
/// If [element] matches this description, records the reference and id.
/// If not, returns `false`, it is a mismatch anyway.
bool match(MatchContext context, Element2 element) {
var enclosingElement = element.enclosingElement2;
if (enclosingElement case LibraryElement2 library) {
if (library.uri == libraryUri && element.name3 == name) {
context.elements.add(element);
if (id case var id?) {
context.externalIds[element] = id;
}
return true;
}
var enclosingElement = element.enclosingElement2!;
Element2 givenTopLevelElement;
Element2? givenMemberElement;
if (enclosingElement is LibraryElement2) {
givenTopLevelElement = element;
} else {
givenTopLevelElement = enclosingElement;
givenMemberElement = element;
}
var givenLibraryUri = enclosingElement.library2!.uri;
if (givenLibraryUri != libraryUri) {
return false;
}
if (givenTopLevelElement.lookupName != topLevelName) {
return false;
}
if (givenMemberElement?.lookupName != memberName) {
return false;
}
// TODO(scheglov): support also not top-level elements.
return false;
context.elements.add(element);
if (id case var id?) {
context.externalIds[element] = id;
}
return true;
}
void write(BufferedSink sink) {
sink.writeUri(libraryUri);
sink.writeStringUtf8(name);
sink.writeStringUtf8(topLevelName);
sink.writeOptionalStringUtf8(memberName);
sink.writeOptionalObject(id, (it) => it.write(sink));
}
@@ -128,13 +148,27 @@ final class ManifestElement {
EncodeContext context,
Element2 element,
) {
// TODO(scheglov): support also not top-level elements.
Element2 topLevelElement;
Element2? memberElement;
var enclosingElement = element.enclosingElement2!;
if (enclosingElement is LibraryElement2) {
topLevelElement = element;
} else {
topLevelElement = enclosingElement;
memberElement = element;
}
return ManifestElement(
libraryUri: element.library2!.uri,
name: element.name3!,
libraryUri: topLevelElement.library2!.uri,
topLevelName: topLevelElement.lookupName!,
memberName: memberElement?.lookupName,
id: context.getElementId(element),
);
}
static List<ManifestElement> readList(SummaryDataReader reader) {
return reader.readTypedList(() => ManifestElement.read(reader));
}
}
class MatchContext {
@@ -192,12 +226,18 @@ class MatchContext {
extension LinkedElementFactoryExtension on LinkedElementFactory {
/// Returns the id of [element], or `null` if from this bundle.
ManifestItemId? getElementId(Element2 element) {
// SAFETY: if we can reference the element, it has a name.
var name = element.lookupName!.asLookupName;
ManifestItemId? getElementId(Element2 element2) {
Element2 topLevelElement;
Element2? memberElement;
if (element2.enclosingElement2 is LibraryElement2) {
topLevelElement = element2;
} else {
topLevelElement = element2.enclosingElement2!;
memberElement = element2;
}
// SAFETY: if we can reference the element, it is in a library.
var libraryUri = element.library2!.uri;
var libraryUri = topLevelElement.library2!.uri;
// Prepare the external library manifest.
var manifest = libraryManifests[libraryUri];
@@ -205,8 +245,27 @@ extension LinkedElementFactoryExtension on LinkedElementFactory {
return null;
}
// SAFETY: every element is in the manifest of the declaring library.
// TODO(scheglov): if we do null assert, it fails, investigate
return manifest.items[name]?.id;
// SAFETY: if we can reference the element, it has a name.
var topLevelName = topLevelElement.lookupName!.asLookupName;
var topLevelItem = manifest.items[topLevelName];
// TODO(scheglov): remove it after supporting all elements
if (topLevelItem == null) {
return null;
}
if (memberElement == null) {
return topLevelItem.id;
}
// TODO(scheglov): When implementation is complete, cast unconditionally.
if (topLevelItem is InstanceItem) {
var memberName = memberElement.lookupName!.asLookupName;
var memberItem = topLevelItem.members[memberName];
// TODO(scheglov): When implementation is complete, null assert.
return memberItem?.id;
}
return null;
}
}
@@ -717,6 +717,7 @@ extension _GetterElementImplExtension on GetterElementImpl {
// TODO(scheglov): support all expressions
switch (constInitializer) {
case BinaryExpression():
case IntegerLiteral():
case SimpleIdentifier():
break;
@@ -9538,10 +9538,271 @@ class A {}
);
}
test_manifest_constInitializer_integerLiteral() async {
test_manifest_constInitializer_binaryExpression() async {
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0 + 1;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
''',
updatedCode: r'''
const a = 0 + 1;
const b = 0;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
b: #M1
''',
);
}
test_manifest_constInitializer_binaryExpression_left_change() async {
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0;
const b = a + 2;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
b: #M1
''',
updatedCode: r'''
const a = 1;
const b = a + 2;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M2
b: #M3
''',
);
}
test_manifest_constInitializer_binaryExpression_left_token() async {
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0 + 1;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
''',
updatedCode: r'''
const a = 2 + 1;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M1
''',
);
}
test_manifest_constInitializer_binaryExpression_operator() async {
await _runLibraryManifestScenario(
initialCode: r'''
class A {
const A();
int operator+(_) {}
}
const a = A();
const x = a + 1;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
A: #M0
+: #M1
a: #M2
x: #M3
''',
updatedCode: r'''
class A {
const A();
double operator+(_) {}
}
const a = A();
const x = a + 1;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
A: #M0
+: #M4
a: #M2
x: #M5
''',
);
}
test_manifest_constInitializer_binaryExpression_operator_token() async {
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0 + 1;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
''',
updatedCode: r'''
const a = 0 - 1;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M1
''',
);
}
test_manifest_constInitializer_binaryExpression_right() async {
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0;
const b = 2 + a;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
b: #M1
''',
updatedCode: r'''
const a = 1;
const b = 2 + a;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M2
b: #M3
''',
);
}
test_manifest_constInitializer_binaryExpression_right_add() async {
configuration.withElementManifests = true;
await _runLibraryManifestScenario(
initialCode: r'''
const b = 0 + a;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
b: #M0
returnType: double @ dart:core
constInitializer
tokenBuffer: 0+a
tokenLengthList: [1, 1, 1]
elements
[1] (dart:core, num, +) #M1
elementIndexList: [1, 0]
''',
updatedCode: r'''
const a = 1;
const b = 0 + a;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M2
returnType: int @ dart:core
constInitializer
tokenBuffer: 1
tokenLengthList: [1]
b: #M3
returnType: int @ dart:core
constInitializer
tokenBuffer: 0+a
tokenLengthList: [1, 1, 1]
elements
[1] (dart:core, num, +) #M1
[2] (package:test/test.dart, a) <null>
elementIndexList: [1, 2]
''',
);
}
test_manifest_constInitializer_binaryExpression_right_remove() async {
configuration.withElementManifests = true;
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0;
const b = 1 + a;
''',
expectedInitialEvents: r'''
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/test.dart
manifest
a: #M0
returnType: int @ dart:core
constInitializer
tokenBuffer: 0
tokenLengthList: [1]
b: #M1
returnType: int @ dart:core
constInitializer
tokenBuffer: 1+a
tokenLengthList: [1, 1, 1]
elements
[1] (dart:core, num, +) #M2
[2] (package:test/test.dart, a) <null>
elementIndexList: [1, 2]
''',
updatedCode: r'''
const b = 1 + a;
''',
expectedUpdatedEvents: r'''
[operation] linkLibraryCycle
package:test/test.dart
manifest
b: #M3
returnType: double @ dart:core
constInitializer
tokenBuffer: 1+a
tokenLengthList: [1, 1, 1]
elements
[1] (dart:core, num, +) #M2
elementIndexList: [1, 0]
''',
);
}
test_manifest_constInitializer_integerLiteral() async {
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0;
''',
expectedInitialEvents: r'''
@@ -9550,9 +9811,6 @@ const a = 0;
package:test/test.dart
manifest
a: #M0
returnType: int @ dart:core
constInitializer: ManifestNodeIntegerLiteral
value: 0
''',
updatedCode: r'''
const a = 0;
@@ -9563,19 +9821,12 @@ const b = 1;
package:test/test.dart
manifest
a: #M0
returnType: int @ dart:core
constInitializer: ManifestNodeIntegerLiteral
value: 0
b: #M1
returnType: int @ dart:core
constInitializer: ManifestNodeIntegerLiteral
value: 1
''',
);
}
test_manifest_constInitializer_integerLiteral_value() async {
configuration.withElementManifests = true;
await _runLibraryManifestScenario(
initialCode: r'''
const a = 0;
@@ -9586,9 +9837,6 @@ const a = 0;
package:test/test.dart
manifest
a: #M0
returnType: int @ dart:core
constInitializer: ManifestNodeIntegerLiteral
value: 0
''',
updatedCode: r'''
const a = 1;
@@ -9598,9 +9846,6 @@ const a = 1;
package:test/test.dart
manifest
a: #M1
returnType: int @ dart:core
constInitializer: ManifestNodeIntegerLiteral
value: 1
''',
);
}
@@ -15,6 +15,7 @@ import 'package:analyzer/src/dart/analysis/status.dart';
import 'package:analyzer/src/fine/library_manifest.dart';
import 'package:analyzer/src/fine/lookup_name.dart';
import 'package:analyzer/src/fine/manifest_ast.dart';
import 'package:analyzer/src/fine/manifest_context.dart';
import 'package:analyzer/src/fine/manifest_id.dart';
import 'package:analyzer/src/fine/manifest_item.dart';
import 'package:analyzer/src/fine/manifest_type.dart';
@@ -794,6 +795,16 @@ class LibraryManifestPrinter {
}
}
void _writelnElement(ManifestElement element) {
var parts = [
element.libraryUri,
element.topLevelName,
if (element.memberName case var memberName?) memberName,
];
var idStr = idProvider.manifestId(element.id);
sink.writeln('(${parts.join(', ')}) $idStr');
}
void _writeNamedId(LookupName name, ManifestItemId id) {
var idStr = idProvider.manifestId(id);
sink.writelnWithIndent('$name: $idStr');
@@ -810,20 +821,25 @@ class LibraryManifestPrinter {
void _writeNode(String name, ManifestNode? node) {
if (node != null) {
sink.writeWithIndent('$name: ');
switch (node) {
case ManifestNodeAnnotation():
// TODO(scheglov): Handle this case.
throw UnimplementedError();
case ManifestNodeIntegerLiteral():
sink.writeln('ManifestNodeIntegerLiteral');
sink.writelnWithIndent(name);
sink.withIndent(() {
sink.writelnWithIndent('tokenBuffer: ${node.tokenBuffer}');
sink.writelnWithIndent('tokenLengthList: ${node.tokenLengthList}');
if (node.elements.isNotEmpty) {
sink.writelnWithIndent('elements');
sink.withIndent(() {
sink.writelnWithIndent('value: ${node.value}');
for (var (index, element) in node.elements.indexed) {
sink.writeWithIndent('[${1 + index}] ');
_writelnElement(element);
}
});
case ManifestNodeSimpleIdentifier():
// TODO(scheglov): Handle this case.
throw UnimplementedError();
}
}
if (node.elementIndexList.isNotEmpty) {
sink.writelnWithIndent('elementIndexList: ${node.elementIndexList}');
}
});
}
}
@@ -884,7 +900,7 @@ class LibraryManifestPrinter {
});
case ManifestInterfaceType():
var element = type.element;
sink.write(element.name);
sink.write(element.topLevelName);
writeNullabilitySuffix();
sink.write(' @ ');
sink.writeln(element.libraryUri);