diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart index c53017b7210..7040b1b4515 100644 --- a/pkg/analyzer/lib/src/dart/element/element.dart +++ b/pkg/analyzer/lib/src/dart/element/element.dart @@ -4535,7 +4535,10 @@ abstract class InstanceElementImpl2 extends ElementImpl2 MetadataImpl get metadata2 => firstFragment.metadata2; @override + @trackedDirectlyExpensive List 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') diff --git a/pkg/analyzer/lib/src/fine/manifest_id.dart b/pkg/analyzer/lib/src/fine/manifest_id.dart index 30cca98c76c..27a29e6d7ab 100644 --- a/pkg/analyzer/lib/src/fine/manifest_id.dart +++ b/pkg/analyzer/lib/src/fine/manifest_id.dart @@ -82,6 +82,10 @@ class ManifestItemIdList { const ListEquality().equals(other.ids, ids); } + bool equalToIterable(Iterable other) { + return const IterableEquality().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)); + } +} diff --git a/pkg/analyzer/lib/src/fine/requirement_failure.dart b/pkg/analyzer/lib/src/fine/requirement_failure.dart index 23e4c231c48..c2f3e2364f8 100644 --- a/pkg/analyzer/lib/src/fine/requirement_failure.dart +++ b/pkg/analyzer/lib/src/fine/requirement_failure.dart @@ -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; diff --git a/pkg/analyzer/lib/src/fine/requirements.dart b/pkg/analyzer/lib/src/fine/requirements.dart index df703179faa..8f9d816b9f2 100644 --- a/pkg/analyzer/lib/src/fine/requirements.dart +++ b/pkg/analyzer/lib/src/fine/requirements.dart @@ -202,11 +202,14 @@ class InstanceItemRequirements { final Map requestedSetters; final Map 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 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({ + 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 map) { writeMap( diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart index 375d8545685..9dd53a60ba3 100644 --- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart +++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart @@ -6654,6 +6654,8 @@ class B extends A { A: #M0 named: instances + package:test/a.dart + A package:test/test.dart B requestedMethods @@ -6734,6 +6736,8 @@ class A { A: #M0 named: 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: + 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: + 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: + allDeclaredMethods: #M1 B requestedMethods noSuchMethod: @@ -10356,6 +10486,7 @@ void f (B b) { A requestedMethods noSuchMethod: + allDeclaredMethods: #M5 B requestedMethods noSuchMethod: @@ -10422,6 +10553,7 @@ void f(B b) { A requestedMethods noSuchMethod: + allDeclaredMethods: #M1 B requestedMethods noSuchMethod: @@ -10475,6 +10607,7 @@ void f(B b) { A requestedMethods noSuchMethod: + allDeclaredMethods: #M5 B requestedMethods noSuchMethod: @@ -10552,6 +10685,7 @@ void f(B b) { A requestedMethods noSuchMethod: + allDeclaredMethods: #M2 [status] idle ''', updateFiles: () { @@ -10619,6 +10753,7 @@ void f(B b) { requestedMethods _foo: noSuchMethod: + 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: + 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 { diff --git a/pkg/analyzer/test/src/dart/analysis/result_printer.dart b/pkg/analyzer/test/src/dart/analysis/result_printer.dart index 1d17f90b3c1..7d03aa196d9 100644 --- a/pkg/analyzer/test/src/dart/analysis/result_printer.dart +++ b/pkg/analyzer/test/src/dart/analysis/result_printer.dart @@ -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 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'));