blob: 71da484b177bd083ed5ddbc1053b92626ca17eeb [file] [log] [blame]
// Copyright (c) 2024, 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:crypto/crypto.dart';
import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';
import 'macro_host.dart';
class MacroResultsCache {
final _cache = <_MacroResultsCacheKey, _MacroResultsCacheValue>{};
final QueryService queryService;
MacroResultsCache(this.queryService);
/// Adds an entry into the cache.
///
/// The cache keys are a combined value of the [annotation], and [request].
///
/// The caceh values are based on each query in [queryResults] and a digest of
/// the [Model] response for that query.
void cache({
required QualifiedName annotation,
required AugmentRequest request,
required Iterable<({Query query, Model response})> queryResults,
required AugmentResponse response,
}) {
_cache[(
annotation: annotation.asRecord,
target: request.target.asRecord,
phase: request.phase,
)] = (
queries: queryResults.map((q) => q.query),
resultsDigest:
queryResults
.skip(1)
.fold(
queryResults.first.response,
(model, next) => model.mergeWith(next.response),
)
.digest,
response: response,
);
}
/// Returns a cached result for the given augmentation request, if there is
/// one and it is still valid.
///
/// Otherwise, invalidates the cache for this request and returns `null`.
Future<AugmentResponse?> cachedResult(
QualifiedName annotation,
AugmentRequest request,
) async {
final cacheKey = (
annotation: annotation.asRecord,
target: request.target.asRecord,
phase: request.phase,
);
final cached = _cache[cacheKey];
if (cached == null) return null;
final queryResults = await Scope.query.run(
() => Future.wait(
cached.queries.map(
(query) => queryService.handle(QueryRequest(query: query)),
),
),
);
final newResultsDigest =
queryResults
.skip(1)
.fold(
queryResults.first.model,
(model, next) => model.mergeWith(next.model),
)
.digest;
if (newResultsDigest != cached.resultsDigest) {
_cache.remove(cacheKey);
return null;
}
return cached.response;
}
}
typedef _MacroResultsCacheKey =
({QualifiedNameRecord annotation, QualifiedNameRecord target, int phase});
typedef _MacroResultsCacheValue =
({
/// All queries done by a macro in a given phase.
Iterable<Query> queries,
/// The [Digest] of the merged model from all query responses.
Digest resultsDigest,
/// The macro augmentation response that was cached.
AugmentResponse response,
});
typedef QualifiedNameRecord =
(String uri, String? scope, String name, bool? isStatic);
extension on QualifiedName {
QualifiedNameRecord get asRecord => (uri, scope, name, isStatic);
}