Fine. Record InstanceElement.methods access.

Change-Id: I6451b6d3f1bed1624f3d9e3c8e0549615b716678
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430940
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov
2025-05-27 10:06:59 -07:00
committed by Commit Queue
parent 03515b472e
commit f3a4e5bc54
6 changed files with 309 additions and 3 deletions
+12 -1
View File
@@ -4535,7 +4535,10 @@ abstract class InstanceElementImpl2 extends ElementImpl2
MetadataImpl get metadata2 => firstFragment.metadata2;
@override
@trackedDirectlyExpensive
List<MethodElementImpl2> get methods {
globalResultRequirements?.record_instanceElement_methods(element: this);
return firstFragment.methods.map((e) => e.asElement2).toList();
}
@@ -4613,7 +4616,15 @@ abstract class InstanceElementImpl2 extends ElementImpl2
name: name,
);
return methods.firstWhereOrNull((e) => e.lookupName == name);
return globalResultRequirements.withoutRecording(
reason: r'''
The result depends only on the requested method, which we have already
recorded above.
''',
operation: () {
return methods.firstWhereOrNull((e) => e.lookupName == name);
},
);
}
@Deprecated('Use getMethod instead')
+16 -2
View File
@@ -82,6 +82,10 @@ class ManifestItemIdList {
const ListEquality<ManifestItemId>().equals(other.ids, ids);
}
bool equalToIterable(Iterable<ManifestItemId> other) {
return const IterableEquality<ManifestItemId>().equals(ids, other);
}
@override
String toString() {
return '[${ids.join(', ')}]';
@@ -90,12 +94,22 @@ class ManifestItemIdList {
void write(BufferedSink sink) {
sink.writeList(ids, (id) => id.write(sink));
}
static ManifestItemIdList? readOptional(SummaryDataReader reader) {
return reader.readOptionalObject(() => ManifestItemIdList.read(reader));
}
}
extension ManifestItemIdExtension on ManifestItemId? {
void writeOptional(BufferedSink sink) {
sink.writeOptionalObject(this, (id) {
id.write(sink);
sink.writeOptionalObject(this, (it) {
it.write(sink);
});
}
}
extension ManifestItemIdListOrNullExtension on ManifestItemIdList? {
void writeOptional(BufferedSink sink) {
sink.writeOptionalObject(this, (it) => it.write(sink));
}
}
@@ -76,6 +76,20 @@ class InstanceMethodIdMismatch extends RequirementFailure {
});
}
class InstanceMethodIdsMismatch extends RequirementFailure {
final Uri libraryUri;
final LookupName interfaceName;
final ManifestItemIdList expectedIds;
final ManifestItemIdList actualIds;
InstanceMethodIdsMismatch({
required this.libraryUri,
required this.interfaceName,
required this.expectedIds,
required this.actualIds,
});
}
class InterfaceConstructorIdMismatch extends RequirementFailure {
final Uri libraryUri;
final LookupName interfaceName;
@@ -202,11 +202,14 @@ class InstanceItemRequirements {
final Map<LookupName, ManifestItemId?> requestedSetters;
final Map<LookupName, ManifestItemId?> requestedMethods;
ManifestItemIdList? allDeclaredMethods;
InstanceItemRequirements({
required this.requestedFields,
required this.requestedGetters,
required this.requestedSetters,
required this.requestedMethods,
required this.allDeclaredMethods,
});
factory InstanceItemRequirements.empty() {
@@ -215,6 +218,7 @@ class InstanceItemRequirements {
requestedGetters: {},
requestedSetters: {},
requestedMethods: {},
allDeclaredMethods: null,
);
}
@@ -224,6 +228,7 @@ class InstanceItemRequirements {
requestedGetters: reader.readNameToIdMap(),
requestedSetters: reader.readNameToIdMap(),
requestedMethods: reader.readNameToIdMap(),
allDeclaredMethods: ManifestItemIdList.readOptional(reader),
);
}
@@ -232,6 +237,7 @@ class InstanceItemRequirements {
sink.writeNameToIdMap(requestedGetters);
sink.writeNameToIdMap(requestedSetters);
sink.writeNameToIdMap(requestedMethods);
allDeclaredMethods.writeOptional(sink);
}
}
@@ -321,6 +327,8 @@ class RequirementsManifest {
final List<LibraryExportRequirements> exportRequirements = [];
int _recordingLockLevel = 0;
RequirementsManifest();
factory RequirementsManifest.read(SummaryDataReader reader) {
@@ -491,6 +499,19 @@ class RequirementsManifest {
);
}
}
if (requirements.allDeclaredMethods case var required?) {
var actualItems = instanceItem.declaredMethods.values;
var actualIds = actualItems.map((item) => item.id);
if (!required.equalToIterable(actualIds)) {
return InstanceMethodIdsMismatch(
libraryUri: libraryUri,
interfaceName: instanceName,
expectedIds: required,
actualIds: ManifestItemIdList(actualIds.toList()),
);
}
}
}
}
@@ -666,6 +687,24 @@ class RequirementsManifest {
requirements.requestedSetters[methodName] = methodId;
}
void record_instanceElement_methods({required InstanceElementImpl2 element}) {
if (_recordingLockLevel != 0) {
return;
}
var itemRequirements = _getInstanceItem(element);
if (itemRequirements == null) {
return;
}
var item = itemRequirements.item;
var requirements = itemRequirements.requirements;
requirements.allDeclaredMethods ??= ManifestItemIdList(
item.declaredMethods.values.map((item) => item.id).toList(),
);
}
/// Record that a member with [nameObj] was requested from the interface
/// of [element]. The [methodElement] is used for consistency checking.
void record_interface_getMember({
@@ -973,6 +1012,25 @@ class _InterfaceItemWithRequirements {
});
}
extension RequirementsManifestExtension on RequirementsManifest? {
T withoutRecording<T>({
required String reason,
required T Function() operation,
}) {
var self = this;
if (self == null) {
return operation();
} else {
self._recordingLockLevel++;
try {
return operation();
} finally {
self._recordingLockLevel--;
}
}
}
}
extension _BufferedSinkExtension on BufferedSink {
void writeNameToIdMap(Map<LookupName, ManifestItemId?> map) {
writeMap(
@@ -6654,6 +6654,8 @@ class B extends A {
A: #M0
named: <null>
instances
package:test/a.dart
A
package:test/test.dart
B
requestedMethods
@@ -6734,6 +6736,8 @@ class A {
A: #M0
named: <null>
instances
package:test/a.dart
A
package:test/test.dart
B
requestedMethods
@@ -7832,6 +7836,131 @@ class A {
);
}
test_dependency_class_declared_methods_add() async {
_ManualRequirements.install((state) {
var A = state.singleUnit.scopeInstanceElement('A');
A.methods;
});
await _runChangeScenarioTA(
initialA: r'''
class A {
void foo() {}
}
''',
testCode: r'''
import 'a.dart';
''',
operation: _FineOperationTestFileGetErrors(),
expectedInitialEvents: r'''
[status] working
[operation] linkLibraryCycle SDK
[future] getErrors T1
ErrorsResult #0
path: /home/test/lib/test.dart
uri: package:test/test.dart
flags: isLibrary
errors
7 +8 UNUSED_IMPORT
[operation] linkLibraryCycle
package:test/a.dart
declaredClasses
A: #M0
declaredMethods
foo: #M1
interface
map
foo: #M1
requirements
[operation] linkLibraryCycle
package:test/test.dart
requirements
[operation] analyzeFile
file: /home/test/lib/test.dart
library: /home/test/lib/test.dart
[stream]
ResolvedUnitResult #1
path: /home/test/lib/test.dart
uri: package:test/test.dart
flags: exists isLibrary
errors
7 +8 UNUSED_IMPORT
[operation] analyzedLibrary
file: /home/test/lib/test.dart
requirements
topLevels
dart:core
A: <null>
package:test/a.dart
A: #M0
instances
package:test/a.dart
A
allDeclaredMethods: #M1
[status] idle
''',
updatedA: r'''
class A {
void foo() {}
void bar() {}
}
''',
expectedUpdatedEvents: r'''
[status] working
[operation] linkLibraryCycle
package:test/a.dart
declaredClasses
A: #M0
declaredMethods
bar: #M2
foo: #M1
interface
map
bar: #M2
foo: #M1
requirements
[future] getErrors T2
ErrorsResult #2
path: /home/test/lib/test.dart
uri: package:test/test.dart
flags: isLibrary
errors
7 +8 UNUSED_IMPORT
[operation] readLibraryCycleBundle
package:test/test.dart
[operation] getErrorsCannotReuse
instanceMethodIdsMismatch
libraryUri: package:test/a.dart
interfaceName: A
expectedIds: #M1
actualIds: #M1 #M2
[operation] analyzeFile
file: /home/test/lib/test.dart
library: /home/test/lib/test.dart
[stream]
ResolvedUnitResult #3
path: /home/test/lib/test.dart
uri: package:test/test.dart
flags: exists isLibrary
errors
7 +8 UNUSED_IMPORT
[operation] analyzedLibrary
file: /home/test/lib/test.dart
requirements
topLevels
dart:core
A: <null>
package:test/a.dart
A: #M0
instances
package:test/a.dart
A
allDeclaredMethods: #M1 #M2
[status] idle
''',
);
}
test_dependency_class_declared_setter() async {
_ManualRequirements.install((state) {
var A = state.singleUnit.scopeInstanceElement('A');
@@ -10301,6 +10430,7 @@ void f (B b) {
A
requestedMethods
noSuchMethod: <null>
allDeclaredMethods: #M1
B
requestedMethods
noSuchMethod: <null>
@@ -10356,6 +10486,7 @@ void f (B b) {
A
requestedMethods
noSuchMethod: <null>
allDeclaredMethods: #M5
B
requestedMethods
noSuchMethod: <null>
@@ -10422,6 +10553,7 @@ void f(B b) {
A
requestedMethods
noSuchMethod: <null>
allDeclaredMethods: #M1
B
requestedMethods
noSuchMethod: <null>
@@ -10475,6 +10607,7 @@ void f(B b) {
A
requestedMethods
noSuchMethod: <null>
allDeclaredMethods: #M5
B
requestedMethods
noSuchMethod: <null>
@@ -10552,6 +10685,7 @@ void f(B b) {
A
requestedMethods
noSuchMethod: <null>
allDeclaredMethods: #M2
[status] idle
''',
updateFiles: () {
@@ -10619,6 +10753,7 @@ void f(B b) {
requestedMethods
_foo: <null>
noSuchMethod: <null>
allDeclaredMethods: #M4
[status] idle
''',
);
@@ -44807,6 +44942,56 @@ import 'a.dart';
);
}
test_req_instanceElement_methods() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
static int foo() {}
}
''');
newFile(testFile.path, r'''
import 'a.dart';
''');
_ManualRequirements.install((state) {
var A = state.singleUnit.scopeInstanceElement('A');
A.methods;
});
await _runManualRequirementsRecording(
expectedEvents: r'''
[status] working
[operation] linkLibraryCycle SDK
[operation] linkLibraryCycle
package:test/a.dart
declaredClasses
A: #M0
declaredMethods
foo: #M1
requirements
topLevels
dart:core
int: #M2
[operation] linkLibraryCycle
package:test/test.dart
requirements
[operation] analyzedLibrary
file: /home/test/lib/test.dart
requirements
topLevels
dart:core
A: <null>
package:test/a.dart
A: #M0
instances
package:test/a.dart
A
allDeclaredMethods: #M1
[status] idle
''',
);
}
test_req_instanceElement_setterElement_variable() async {
newFile('$testPackageLibPath/a.dart', r'''
class A {
@@ -140,6 +140,16 @@ class BundleRequirementsPrinter {
_writeNamedId,
);
});
sink.withIndent(() {
var idList = instanceEntry.value.allDeclaredMethods;
if (idList != null) {
if (idList.ids.isNotEmpty) {
var idListStr = idList.asString(idProvider);
sink.writelnWithIndent('allDeclaredMethods: $idListStr');
}
}
});
});
});
}
@@ -486,6 +496,14 @@ class DriverEventsPrinter {
'expectedId': idProvider.manifestId(failure.expectedId),
'actualId': idProvider.manifestId(failure.actualId),
});
case InstanceMethodIdsMismatch():
sink.writelnWithIndent('instanceMethodIdsMismatch');
sink.writeProperties({
'libraryUri': failure.libraryUri,
'interfaceName': failure.interfaceName.asString,
'expectedIds': failure.expectedIds.asString(idProvider),
'actualIds': failure.actualIds.asString(idProvider),
});
case InterfaceConstructorIdMismatch():
sink.writelnWithIndent('interfaceConstructorIdMismatch');
sink.writeProperties({
@@ -1582,6 +1600,12 @@ class UnitElementPrinterConfiguration {
List<Element> Function(LibraryFragment) elementSelector = (_) => [];
}
extension on ManifestItemIdList {
String asString(IdProvider idProvider) {
return ids.map((id) => idProvider.manifestId(id)).join(' ');
}
}
extension on LibraryCycle {
bool get isSdk {
return libraries.any((library) => library.file.uri.isScheme('dart'));