blob: be8b6f7b2f850e69270752bb9698c74762b9f311 [file] [edit]
// 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 'dart_model.g.dart';
import 'json_buffer/json_buffer_builder.dart';
import 'lazy_merged_map.dart';
import 'scopes.dart';
export 'dart_model.g.dart';
extension QualifiedNameExtension on QualifiedName {
String get asString =>
'$uri#${scope == null ? '' : '$scope${isStatic! ? '::' : '.'}'}$name';
bool equals(QualifiedName other) =>
other.uri == uri &&
other.name == name &&
other.scope == scope &&
other.isStatic == isStatic;
}
extension ParentInterface on Member {
QualifiedName get parentInterface {
final self = MacroScope.current.model.qualifiedNameOf(node)!;
return QualifiedName(uri: self.uri, name: self.scope);
}
}
extension ModelExtension on Model {
/// An identity hash for `this`, used for comparing query results.
///
/// TODO: A faster/better implementation?
Digest get digest {
// TODO: Implementation for non-buffer maps?
var node = this.node as MapInBuffer;
return node.buffer.digest(
node.pointer,
type: Type.typedMapPointer,
alreadyDereferenced: true,
);
}
/// Looks up [name] in `this`.
///
/// It is on the user to cast this to a proper extension type.
///
/// Returns null if it is not present.
// TODO: return a `Declaration` interface?
Map<String, Object?>? lookup(QualifiedName name) {
final library = uris[name.uri];
if (library == null) return null;
if (name.scope == null) {
throw UnsupportedError(
'Can only look up names in nested scopes for now.',
);
}
final scope = library.scopes[name.scope!];
if (scope == null) return null;
return scope.members[name.name]?.node;
}
/// Returns the path in the model to [node], or `null` if
/// [node] is not in this [Model].
///
/// Comparison is by identity, not by value, so the exact instance must be in
/// this [Model].
///
/// TODO: Should we create a base type called `Declaration` which is
/// implemented by the types which are valid to pass here?
QualifiedName? qualifiedNameOf(Map<String, Object?> node) =>
_qualifiedNameOf(node);
/// Returns the [QualifiedName] in the model to [node], or `null` if [node]
/// is not in this [Model].
QualifiedName? _qualifiedNameOf(Map<String, Object?> model) {
var parent = _getParent(model);
if (parent == null) return null;
final path = <String>[];
path.add(_keyOf(model, parent));
var previousParent = parent;
// Checks if any merged map of `left` == any merged map of `right.
bool isEqualNested(Map<String, Object?> left, Map<String, Object?> right) {
if (left == right) return true;
return left.expand.any((l) => right.expand.contains(l));
}
while (true) {
parent = _getParent(previousParent);
if (parent == null) return null;
/// We reached this models node, stop searching higher.
if (isEqualNested(parent, node)) break;
path.insert(0, _keyOf(previousParent, parent));
previousParent = parent;
}
if (path case [final uri, 'scopes', final name]) {
return QualifiedName(uri: uri, name: name);
} else if (path case [
final uri,
'scopes',
final scope,
'members',
final name,
]) {
return QualifiedName(
uri: uri,
scope: scope,
name: name,
isStatic: Member.fromJson(model).properties.isStatic,
);
}
throw UnsupportedError(
'Unsupported node type for `qualifiedNameOf`, only top level members '
'are supported for now. $path',
);
}
/// Returns the key of [value] in [map].
///
/// Throws if [value] is not in [map].
String _keyOf(Object value, Map<String, Object?> map) {
for (final entry in map.expandWithMerged.expand((map) => map.entries)) {
if (entry.value == value) return entry.key;
}
throw ArgumentError('Value not in map: $value, $map');
}
/// Gets the `Map` that contains [child], or `null` if there isn't one.
Map<String, Object?>? _getParent(Map<String, Object?> child) {
// If both maps are in the same `JsonBufferBuilder` then the parent is
// immediately available.
final childMaps = child.expand;
final childBufferMaps = childMaps.whereType<MapInBuffer>();
for (final thisMapInBuffer in node.expand.whereType<MapInBuffer>()) {
for (final thatMapInBuffer in childBufferMaps) {
if (thisMapInBuffer.buffer == thatMapInBuffer.buffer) {
return thatMapInBuffer.parent;
}
}
}
// Otherwise, build a `Map` of references to parents and use that.
return _lazyParentsMap[child];
}
/// Gets a `Map` from values to parent `Map`s.
Map<Map<String, Object?>, Map<String, Object?>> get _lazyParentsMap {
var result = _parentsMaps[this];
if (result == null) {
result =
_parentsMaps[this] = <Map<String, Object?>, Map<String, Object?>>{};
_buildParentsMap(node, result);
}
return result;
}
/// Builds a `Map` from values to parent `Map`s.
static void _buildParentsMap(
Map<String, Object?> parent,
Map<Map<String, Object?>, Map<String, Object?>> result,
) {
for (final child in parent.values.whereType<Map<String, Object?>>()) {
for (final map in child.expandWithMerged) {
if (result.containsKey(map)) continue;
result[map] = parent;
_buildParentsMap(child, result);
}
}
}
}
/// Expando storing a `Map` from values to parent `Map`s.
final _parentsMaps = Expando<Map<Map<String, Object?>, Map<String, Object?>>>();