blob: 7519d6903fe90b8d0b97dfa4ee966c4be42a6521 [file] [log] [blame]
// Copyright (c) 2025, 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:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/engine.dart' as engine;
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/summary2/reference.dart';
/// Creates a set of mock libraries from the given specifications.
Map<String, LibraryElementImpl> buildLibrariesFromSpec(
engine.AnalysisContext analysisContext,
Reference rootReference,
AnalysisSessionImpl analysisSession,
Map<String, LibrarySpec> specs,
) {
var builder = _LibraryBuilder(
rootReference,
analysisContext,
analysisSession,
specs,
);
return builder.build();
}
class ClassSpec {
final String name;
final List<String> typeParameters;
final String? supertype;
final List<String> interfaces;
final List<ConstructorSpec> constructors;
final List<MethodSpec> methods;
final bool isAbstract;
const ClassSpec({
required this.name,
this.typeParameters = const [],
this.supertype,
this.interfaces = const [],
this.constructors = const [],
this.methods = const [],
this.isAbstract = false,
});
}
class ConstructorSpec {
final String name;
final String formalParameters;
final bool isConst;
final bool isFactory;
const ConstructorSpec({
this.name = '',
this.formalParameters = '',
this.isConst = false,
this.isFactory = false,
});
}
class FunctionSpec {
final String name;
final List<String> typeParameters;
final String formalParameters;
final String returnType;
const FunctionSpec({
required this.name,
this.typeParameters = const [],
this.formalParameters = '',
required this.returnType,
});
}
class LibrarySpec {
final String uri;
final List<ClassSpec> classes;
final List<FunctionSpec> functions;
const LibrarySpec({
required this.uri,
this.classes = const [],
this.functions = const [],
});
}
class MethodSpec {
final String name;
final List<String> typeParameters;
final String formalParameters;
final String returnType;
const MethodSpec({
required this.name,
this.typeParameters = const [],
this.formalParameters = '',
required this.returnType,
});
}
/// Builds a set of libraries from a collection of [LibrarySpec]s.
///
/// The builder uses a two-pass process:
///
/// 1. **Shell Creation**: Create all `LibraryElementImpl`, `ClassElementImpl`,
/// etc "shells". This populates the namespace so that type lookups can
/// succeed across library boundaries in the next step.
///
/// 2. **Element Population**: Fill in the details of each element, such as
/// supertypes, interfaces, methods, and parameters. This step resolves
/// all type strings using the shells created in the first pass.
///
/// This two-pass approach allows for resolving inter-library dependencies and
/// cycles (e.g., `dart:core` and `dart:async` referencing each other).
class _LibraryBuilder {
final Reference rootReference;
final engine.AnalysisContext analysisContext;
final AnalysisSessionImpl analysisSession;
final Map<String, LibrarySpec> specs;
final Map<String, LibraryElementImpl> _libraryElements = {};
final Map<String, LibraryFragmentImpl> _libraryFragments = {};
final Map<String, ClassElementImpl> _classElements = {};
late final _Scope _rootScope;
final _TypeParser _typeParser = _TypeParser();
_LibraryBuilder(
this.rootReference,
this.analysisContext,
this.analysisSession,
this.specs,
) {
_rootScope = _Scope.root(_classElements);
}
/// Builds all libraries specified in [specs] and returns them in a map.
Map<String, LibraryElementImpl> build() {
_buildElementShells();
_populateElements();
return _libraryElements;
}
/// Create empty shells for all libraries and elements.
void _buildElementShells() {
for (var libSpec in specs.values) {
var libraryUriStr = libSpec.uri;
var librarySource = analysisContext.sourceFactory.forUri(libraryUriStr)!;
var libraryElement = LibraryElementImpl(
analysisContext,
analysisSession,
libraryUriStr.replaceAll(':', '.'),
0,
0,
FeatureSet.latestLanguageVersion(),
);
var libraryFragment = LibraryFragmentImpl(
library: libraryElement,
source: librarySource,
lineInfo: LineInfo([0]),
);
libraryElement.definingCompilationUnit = libraryFragment;
_libraryElements[libraryUriStr] = libraryElement;
_libraryFragments[libraryUriStr] = libraryFragment;
for (var classSpec in libSpec.classes) {
var fragment = ClassFragmentImpl(name: classSpec.name);
var element = ClassElementImpl(
rootReference.getChild('@class').getChild(classSpec.name),
fragment,
);
libraryFragment.encloseElement(fragment);
libraryElement.addClass(element);
_classElements[classSpec.name] = element;
}
}
}
ConstructorFragmentImpl _createConstructorFragment(
ConstructorSpec spec, {
required ClassElementImpl classElement,
required _Scope classScope,
}) {
var fragment = ConstructorFragmentImpl(name: spec.name);
fragment.isConst = spec.isConst;
fragment.isFactory = spec.isFactory;
var element = ConstructorElementImpl(
name: spec.name,
reference: classElement.reference
.getChild('@constructor')
.getChild(spec.name),
firstFragment: fragment,
);
classElement.addConstructor(element);
var formalParameters = _typeParser.parseFormalParameters(
classScope,
spec.formalParameters,
);
fragment.formalParameters = formalParameters.map((formalParameterElement) {
return formalParameterElement.firstFragment;
}).toList();
return fragment;
}
TopLevelFunctionFragmentImpl _createFunctionFragment(
LibraryElementImpl library,
FunctionSpec spec,
) {
var fragment = TopLevelFunctionFragmentImpl(name: spec.name);
var scope = _Scope.child(_rootScope);
var element = TopLevelFunctionElementImpl(
rootReference.getChild('@function').getChild(spec.name),
fragment,
);
library.addTopLevelFunction(element);
for (var name in spec.typeParameters) {
var tpElement = _createTypeParameterElement(name);
scope.addTypeParameter(tpElement);
fragment.typeParameters.add(tpElement.firstFragment);
}
var formalParameterElements = _typeParser.parseFormalParameters(
scope,
spec.formalParameters,
);
fragment.formalParameters = formalParameterElements.map((
formalParameterElement,
) {
return formalParameterElement.firstFragment;
}).toList();
element.returnType = _typeParser.parse(spec.returnType, scope);
return fragment;
}
MethodFragmentImpl _createMethodFragment(
ClassElementImpl classElement,
MethodSpec spec,
_Scope parentScope,
) {
var fragment = MethodFragmentImpl(name: spec.name);
var scope = _Scope.child(parentScope);
var element = MethodElementImpl(
name: spec.name,
reference: classElement.reference.getChild('@method').getChild(spec.name),
firstFragment: fragment,
);
fragment.typeParameters = spec.typeParameters.map((name) {
var tpElement = _createTypeParameterElement(name);
scope.addTypeParameter(tpElement);
return tpElement.firstFragment;
}).toList();
var formalParameterElements = _typeParser.parseFormalParameters(
scope,
spec.formalParameters,
);
fragment.formalParameters = formalParameterElements.map((
formalParameterElement,
) {
return formalParameterElement.firstFragment;
}).toList();
element.returnType = _typeParser.parse(spec.returnType, scope);
return fragment;
}
TypeParameterElementImpl _createTypeParameterElement(String name) {
var fragment = TypeParameterFragmentImpl(name: name);
return TypeParameterElementImpl(firstFragment: fragment);
}
void _populateClasses(LibrarySpec libSpec) {
for (var classSpec in libSpec.classes) {
var element = _classElements[classSpec.name]!;
var fragment = element.firstFragment;
var scope = _Scope.child(_rootScope);
fragment.typeParameters = classSpec.typeParameters.map((name) {
var tpElement = _createTypeParameterElement(name);
scope.addTypeParameter(tpElement);
return tpElement.firstFragment;
}).toList();
if (classSpec.supertype case var supertypeStr?) {
var supertype = _typeParser.parse(supertypeStr, scope);
element.supertype = supertype as InterfaceTypeImpl;
}
element.interfaces = classSpec.interfaces.map((interfaceStr) {
var interface = _typeParser.parse(interfaceStr, scope);
return interface as InterfaceTypeImpl;
}).toList();
for (var constructorSpec in classSpec.constructors) {
var constructorFragment = _createConstructorFragment(
classElement: element,
classScope: scope,
constructorSpec,
);
fragment.addConstructor(constructorFragment);
}
for (var methodSpec in classSpec.methods) {
var methodFragment = _createMethodFragment(element, methodSpec, scope);
fragment.addMethod(methodFragment);
}
element.methods = fragment.methods.map((f) => f.element).toList();
element.constructors = fragment.constructors
.map((f) => f.element)
.toList();
}
}
/// Populate the shells with types, methods, and other details.
void _populateElements() {
for (var libSpec in specs.values) {
_populateClasses(libSpec);
_populateTopLevelFunctions(libSpec);
}
}
void _populateTopLevelFunctions(LibrarySpec libSpec) {
var libraryElement = _libraryElements[libSpec.uri]!;
var libraryFragment = _libraryFragments[libSpec.uri]!;
for (var functionSpec in libSpec.functions) {
var functionFragment = _createFunctionFragment(
libraryElement,
functionSpec,
);
libraryFragment.addFunction(functionFragment);
}
libraryElement.topLevelFunctions = libraryFragment.functions.map((element) {
return element.element;
}).toList();
}
}
/// Representation of direct type like `void` or `dynamic`.
class _PreExplicitType implements _PreType {
final TypeImpl type;
_PreExplicitType({required this.type});
@override
TypeImpl materialize(_Scope scope) => type;
}
class _PreFormalParameter {
final _PreType type;
final String? name;
final ParameterKind kind;
_PreFormalParameter({
required this.type,
required this.name,
required this.kind,
});
FormalParameterElementImpl materialize(_Scope scope) {
var fragment = FormalParameterFragmentImpl(
name: name,
nameOffset: 0,
parameterKind: kind,
);
var element = FormalParameterElementImpl(fragment);
element.type = type.materialize(scope);
return element;
}
}
class _PreFunctionType implements _PreType {
final List<_PreTypeParameter> typeParameters;
final List<_PreFormalParameter> formalParameters;
final _PreType returnType;
_PreFunctionType({
required this.typeParameters,
required this.formalParameters,
required this.returnType,
});
@override
TypeImpl materialize(_Scope scope) {
// Create a new scope for the function's own type parameters.
var functionScope = _Scope.child(scope);
// Create elements for the function's own type parameters and add to scope.
var typeParameters = this.typeParameters.map((pre) {
var fragment = TypeParameterFragmentImpl(name: pre.name);
var element = TypeParameterElementImpl(firstFragment: fragment);
functionScope.addTypeParameter(element);
return element;
}).toList();
// Now that the type parameters are in scope, materialize their bounds.
for (var i = 0; i < typeParameters.length; i++) {
var pre = this.typeParameters[i];
if (pre.bound case var bound?) {
typeParameters[i].bound = bound.materialize(functionScope);
}
}
var returnType = this.returnType.materialize(functionScope);
var formalParameters = this.formalParameters
.map((p) => p.materialize(functionScope))
.toList();
return FunctionTypeImpl.v2(
returnType: returnType,
typeParameters: typeParameters,
formalParameters: formalParameters,
nullabilitySuffix: NullabilitySuffix.none,
);
}
}
class _PreNamedType implements _PreType {
final String name;
final List<_PreType> args;
_PreNamedType({required this.name, required this.args});
@override
TypeImpl materialize(_Scope scope) {
if (scope.lookupTypeParameter(name) case var element?) {
assert(args.isEmpty);
return TypeParameterTypeImpl(
element: element,
nullabilitySuffix: NullabilitySuffix.none,
);
}
var element = scope.lookupInterface(name);
if (element == null) throw StateError('Unknown type: $name');
return InterfaceTypeImpl(
element: element,
typeArguments: args.map((arg) => arg.materialize(scope)).toList(),
nullabilitySuffix: NullabilitySuffix.none,
);
}
}
/// Makes [inner] type nullable.
class _PreNullableType implements _PreType {
final _PreType inner;
_PreNullableType({required this.inner});
@override
TypeImpl materialize(_Scope scope) {
return inner.materialize(scope).withNullability(NullabilitySuffix.question);
}
}
/// Representation of a [TypeImpl] that has been parsed but hasn't had meaning
/// assigned to its identifiers yet.
abstract class _PreType {
/// Translates `this` into a [TypeImpl].
///
/// The meaning of identifiers in `this` is determined by looking them up
/// in the provided [scope].
TypeImpl materialize(_Scope scope);
}
class _PreTypeParameter {
final String name;
final _PreType? bound;
_PreTypeParameter({required this.name, required this.bound})
: assert(name.isNotEmpty);
}
/// A unified scope for looking up named elements.
///
/// It can look up type parameters from the current (and parent) scopes,
/// as well as named types (like classes and mixins) from the global scope.
class _Scope {
final _Scope? parent;
final Map<String, TypeParameterElementImpl> _typeParameters = {};
final Map<String, InterfaceElementImpl> _interfaces;
/// Creates a nested scope that inherits its parent's context.
///
/// This is used to add type parameters of a class, method, etc.
_Scope.child(_Scope this.parent) : _interfaces = parent._interfaces;
/// Creates a root scope containing all known interfaces.
_Scope.root(this._interfaces) : parent = null;
/// Adds a type parameter to the current scope.
void addTypeParameter(TypeParameterElementImpl element) {
_typeParameters[element.name!] = element;
}
/// Looks up an interface by name.
InterfaceElementImpl? lookupInterface(String name) {
return _interfaces[name];
}
/// Looks up a type parameter by name, walking up the scope chain.
TypeParameterElementImpl? lookupTypeParameter(String name) {
return _typeParameters[name] ?? parent?.lookupTypeParameter(name);
}
}
class _TokenStream {
static final RegExp _tokenizer = RegExp(
r'[a-zA-Z_]\w*|<|>|,|\?|\(|\)|\{|\}|\[|\]|extends',
);
final List<String> _tokens;
int _index = 0;
factory _TokenStream.fromString(String input) {
var tokens = _tokenizer.allMatches(input).map((m) => m.group(0)!).toList();
return _TokenStream._(tokens);
}
_TokenStream._(this._tokens);
bool get isAtEnd => _index >= _tokens.length;
String consume() {
if (isAtEnd) throw StateError('Unexpected end of token stream.');
return _tokens[_index++];
}
void expect(String expected) {
if (isAtEnd) {
throw StateError('Expected "$expected" but found end of stream.');
}
var token = consume();
if (token != expected) {
throw StateError('Expected "$expected" but found "$token".');
}
}
bool match(String expected) {
if (peekIs(expected)) {
_index++;
return true;
}
return false;
}
String peek() {
if (isAtEnd) throw StateError('Unexpected end of token stream.');
return _tokens[_index];
}
bool peekIs(String token) {
return !isAtEnd && peek() == token;
}
bool peekIsAnyOf(Set<String> tokens) {
return !isAtEnd && tokens.contains(peek());
}
}
class _TypeParser {
TypeImpl parse(String input, _Scope scope) {
var stream = _TokenStream.fromString(input);
var preType = _parseType(stream);
if (!stream.isAtEnd) {
throw StateError('Unexpected trailing tokens');
}
return preType.materialize(scope);
}
/// Parse formal parameters, without enclosing `()`.
List<FormalParameterElementImpl> parseFormalParameters(
_Scope scope,
String input,
) {
var stream = _TokenStream.fromString(input);
var preList = _parseFormalParameters(stream);
return preList.map((pre) => pre.materialize(scope)).toList();
}
_PreFormalParameter _parseFormalParameter(
_TokenStream stream,
ParameterKind kind,
) {
if (kind == ParameterKind.NAMED) {
kind = ParameterKind.NAMED_REQUIRED;
}
var type = _parseType(stream);
String? name;
if (!stream.isAtEnd && !stream.peekIsAnyOf(const {',', ')', ']', '}'})) {
name = stream.consume();
}
return _PreFormalParameter(type: type, name: name, kind: kind);
}
/// Parse formal parameters, without enclosing `()`.
List<_PreFormalParameter> _parseFormalParameters(_TokenStream stream) {
var formalParameters = <_PreFormalParameter>[];
if (stream.isAtEnd || stream.peekIs(')')) {
return formalParameters;
}
// Parse required positional formal parameters.
while (!stream.isAtEnd && !stream.peekIsAnyOf(const {')', '[', '{'})) {
formalParameters.add(
_parseFormalParameter(stream, ParameterKind.REQUIRED),
);
stream.match(',');
}
// Parse optional positional formal parameters.
if (stream.match('[')) {
while (!stream.isAtEnd && !stream.peekIs(']')) {
formalParameters.add(
_parseFormalParameter(stream, ParameterKind.POSITIONAL),
);
stream.match(',');
}
stream.expect(']');
}
// Parse named formal parameters.
if (stream.match('{')) {
while (!stream.isAtEnd && !stream.peekIs('}')) {
formalParameters.add(
_parseFormalParameter(stream, ParameterKind.NAMED),
);
stream.match(',');
}
stream.expect('}');
}
return formalParameters;
}
_PreType _parsePrimaryType(_TokenStream stream) {
var name = stream.consume();
switch (name) {
case 'dynamic':
return _PreExplicitType(type: DynamicTypeImpl.instance);
case 'void':
return _PreExplicitType(type: VoidTypeImpl.instance);
}
var args = <_PreType>[];
if (stream.match('<')) {
while (!stream.isAtEnd && !stream.peekIs('>')) {
args.add(_parseType(stream));
stream.match(',');
}
stream.expect('>');
}
return _PreNamedType(name: name, args: args);
}
_PreType _parseType(_TokenStream stream) {
var type = _parsePrimaryType(stream);
// Check for function type.
if (stream.match('Function')) {
var typeParameters = _parseTypeParameters(stream);
stream.expect('(');
var formalParameters = _parseFormalParameters(stream);
stream.expect(')');
type = _PreFunctionType(
returnType: type,
formalParameters: formalParameters,
typeParameters: typeParameters,
);
}
if (stream.match('?')) {
return _PreNullableType(inner: type);
}
return type;
}
List<_PreTypeParameter> _parseTypeParameters(_TokenStream stream) {
var typeParameters = <_PreTypeParameter>[];
if (stream.isAtEnd || !stream.match('<')) {
return typeParameters;
}
while (!stream.isAtEnd && !stream.peekIs('>')) {
var name = stream.consume();
_PreType? bound;
if (stream.match('extends')) {
bound = _parseType(stream);
}
typeParameters.add(_PreTypeParameter(name: name, bound: bound));
stream.match(',');
}
stream.expect('>');
return typeParameters;
}
}