blob: e76df57b1a41232341428dc82b7a39430cc5db5e [file]
// 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:_fe_analyzer_shared/src/types/shared_type.dart' show Variance;
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/dart/element/type_schema.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({
required engine.AnalysisContext analysisContext,
required RootReference rootReference,
required AnalysisSessionImpl analysisSession,
required Map<String, LibrarySpec> specs,
Map<String, LibraryElementImpl> externalLibraries = const {},
}) {
var builder = _LibraryBuilder(
rootReference,
analysisContext,
analysisSession,
specs,
externalLibraries,
);
return builder.build();
}
List<InterfaceTypeImpl> _materializeInterfaceTypes(
Iterable<_ParsedType> types,
_Scope scope,
) {
return [for (var type in types) type.materialize(scope) as InterfaceTypeImpl];
}
class ClassSpec {
final String header;
final List<ConstructorSpec> constructors;
final List<MethodSpec> methods;
const ClassSpec(
this.header, {
this.constructors = const [],
this.methods = const [],
});
}
class ConstructorSpec {
final String header;
const ConstructorSpec(this.header);
}
class EnumSpec {
final String header;
final List<String> constants;
const EnumSpec(this.header, {this.constants = const []});
}
class ExtensionTypeSpec {
final String header;
const ExtensionTypeSpec(this.header);
}
class LibrarySpec {
final String uri;
final List<String> imports;
final List<ClassSpec> classes;
final List<EnumSpec> enums;
final List<ExtensionTypeSpec> extensionTypes;
final List<TopLevelFunctionSpec> functions;
final List<MixinSpec> mixins;
final List<TypeAliasSpec> typeAliases;
const LibrarySpec({
required this.uri,
this.imports = const [],
this.classes = const [],
this.enums = const [],
this.extensionTypes = const [],
this.functions = const [],
this.mixins = const [],
this.typeAliases = const [],
});
}
class MethodSpec {
final String header;
const MethodSpec(this.header);
}
class MixinSpec {
final String header;
const MixinSpec(this.header);
}
class TopLevelFunctionSpec {
final String header;
const TopLevelFunctionSpec(this.header);
}
class TypeAliasSpec {
final String header;
const TypeAliasSpec(this.header);
}
final class TypeSpecParser {
final _Scope _scope;
TypeSpecParser({
required List<LibraryElementImpl> libraries,
required List<TypeParameterElementImpl> typeParameters,
}) : _scope = _Scope.forLibraries(
libraries: libraries,
typeParameters: typeParameters,
);
TypeImpl parse(String input) {
return _SpecParser.parseType(input).materialize(_scope);
}
List<TypeParameterElementImpl> parseTypeParameters(String input) {
var declarations = _TypeParameterDeclarations(
_SpecParser.parseTypeParameters(input),
);
return declarations.materializeStandalone(_Scope.child(_scope));
}
}
class _ClassDeclaration {
final ClassSpec spec;
final _ParsedClassHeader header;
final _TypeParameterDeclarations typeParameters;
final List<_ConstructorDeclaration> constructors;
final List<_MethodDeclaration> methods;
late final ClassFragmentImpl fragment;
late final ClassElementImpl element;
factory _ClassDeclaration.fromSpec(ClassSpec spec) {
var header = _SpecParser.parseClassHeader(spec.header);
return _ClassDeclaration._(spec, header);
}
_ClassDeclaration._(this.spec, this.header)
: typeParameters = _TypeParameterDeclarations(header.typeParameters),
constructors = [
for (var constructorSpec in spec.constructors)
_ConstructorDeclaration.fromSpec(constructorSpec),
],
methods = [
for (var methodSpec in spec.methods)
_MethodDeclaration.fromSpec(methodSpec),
];
void createElement({required LibraryReference libraryReference}) {
fragment = ClassFragmentImpl(name: header.name)
..typeParameters = typeParameters.createFragments()
..isAbstract = header.isAbstract
..isSealed = header.isSealed;
element = ClassElementImpl(
libraryReference.declareClass(header.name),
fragment,
);
for (var constructorDeclaration in constructors) {
constructorDeclaration.createElement(classElement: element);
fragment.addConstructor(constructorDeclaration.fragment);
element.addConstructor(constructorDeclaration.element);
}
for (var methodDeclaration in methods) {
methodDeclaration.createElement(classElement: element);
fragment.addMethod(methodDeclaration.fragment);
element.addMethod(methodDeclaration.element);
}
}
void resolve(_Scope libraryScope) {
var scope = _Scope.child(libraryScope);
typeParameters.resolve(scope);
element.supertype =
header.supertype.materialize(scope) as InterfaceTypeImpl;
element.mixins = _materializeInterfaceTypes(header.mixins, scope);
element.interfaces = _materializeInterfaceTypes(header.interfaces, scope);
for (var constructorDeclaration in constructors) {
constructorDeclaration.resolve(scope);
}
for (var methodDeclaration in methods) {
methodDeclaration.resolve(scope);
}
}
}
class _ConstructorDeclaration {
final ConstructorSpec spec;
final _ParsedConstructorHeader header;
final _FormalParameterDeclarations formalParameters;
late final ConstructorFragmentImpl fragment;
late final ConstructorElementImpl element;
factory _ConstructorDeclaration.fromSpec(ConstructorSpec spec) {
var header = _SpecParser.parseConstructorHeader(spec.header);
return _ConstructorDeclaration._(spec, header);
}
_ConstructorDeclaration._(this.spec, this.header)
: formalParameters = _FormalParameterDeclarations(header.formalParameters);
void createElement({required ClassElementImpl classElement}) {
fragment = ConstructorFragmentImpl(name: header.name)
..formalParameters = formalParameters.createFragments()
..isOriginDeclaration = true
..isConst = header.isConst
..isFactory = header.isFactory;
element = ConstructorElementImpl(
name: header.name,
reference: classElement.reference.declareConstructor(header.name),
firstFragment: fragment,
);
formalParameters.createElements();
}
void resolve(_Scope scope) {
formalParameters.resolve(scope);
}
}
class _EnumDeclaration {
final EnumSpec spec;
final _ParsedEnumHeader header;
late final EnumFragmentImpl fragment;
late final EnumElementImpl element;
factory _EnumDeclaration.fromSpec(EnumSpec spec) {
var header = _SpecParser.parseEnumHeader(spec.header);
return _EnumDeclaration._(spec, header);
}
_EnumDeclaration._(this.spec, this.header);
void createElement({required LibraryReference libraryReference}) {
fragment = EnumFragmentImpl(name: header.name);
fragment.fields = [
for (var name in spec.constants)
FieldFragmentImpl(name: name)
..isEnumConstant = true
..isOriginDeclaration = true,
];
element = EnumElementImpl(
libraryReference.declareEnum(header.name),
fragment,
);
}
void resolve(_Scope libraryScope) {
element.mixins = _materializeInterfaceTypes(header.mixins, libraryScope);
element.interfaces = _materializeInterfaceTypes(
header.interfaces,
libraryScope,
);
}
}
enum _ExecutableHeaderContext {
method(
typeParameterContext: _TypeParameterContext.methodDeclaration,
allowsOperators: true,
),
topLevelFunction(
typeParameterContext: _TypeParameterContext.topLevelFunctionDeclaration,
allowsOperators: false,
);
final _TypeParameterContext typeParameterContext;
final bool allowsOperators;
const _ExecutableHeaderContext({
required this.typeParameterContext,
required this.allowsOperators,
});
}
class _ExtensionTypeDeclaration {
final ExtensionTypeSpec spec;
final _ParsedExtensionTypeHeader header;
final _TypeParameterDeclarations typeParameters;
late final ExtensionTypeFragmentImpl fragment;
late final ExtensionTypeElementImpl element;
factory _ExtensionTypeDeclaration.fromSpec(ExtensionTypeSpec spec) {
var header = _SpecParser.parseExtensionTypeHeader(spec.header);
return _ExtensionTypeDeclaration._(spec, header);
}
_ExtensionTypeDeclaration._(this.spec, this.header)
: typeParameters = _TypeParameterDeclarations(header.typeParameters);
void createElement({required LibraryReference libraryReference}) {
var fieldFragment = FieldFragmentImpl(name: header.representationName)
..isOriginDeclaringFormalParameter = true;
fragment = ExtensionTypeFragmentImpl(name: header.name)
..typeParameters = typeParameters.createFragments()
..fields = [fieldFragment];
element = ExtensionTypeElementImpl(
libraryReference.declareExtensionType(header.name),
fragment,
);
var fieldElement = FieldElementImpl(
reference: element.reference.declareField(header.representationName),
firstFragment: fieldFragment,
);
element.fields = [fieldElement];
}
void resolve(_Scope libraryScope) {
var scope = _Scope.child(libraryScope);
typeParameters.resolve(scope);
var representationType = header.representationType.materialize(scope);
element.typeErasure = representationType;
element.fields.single.type = representationType;
element.interfaces = _materializeInterfaceTypes(header.interfaces, scope);
}
}
class _FormalParameterDeclaration {
final _ParsedFormalParameter parsed;
late final FormalParameterFragmentImpl fragment;
_FormalParameterDeclaration(this.parsed);
FormalParameterElementImpl get element => fragment.element;
void createElement() {
fragment.initElement();
}
FormalParameterFragmentImpl createFragment() {
return fragment = FormalParameterFragmentImpl(
name: parsed.name,
nameOffset: 0,
parameterKind: parsed.kind,
)..isExplicitlyCovariant = parsed.isCovariant;
}
void resolve(_Scope scope) {
fragment.element.type = parsed.type.materialize(scope);
}
}
class _FormalParameterDeclarations {
final List<_FormalParameterDeclaration> _declarations;
_FormalParameterDeclarations(
Iterable<_ParsedFormalParameter> formalParameters,
) : _declarations = [
for (var parsed in formalParameters)
_FormalParameterDeclaration(parsed),
];
void createElements() {
for (var declaration in _declarations) {
declaration.createElement();
}
}
List<FormalParameterFragmentImpl> createFragments() {
return [
for (var declaration in _declarations) declaration.createFragment(),
];
}
List<FormalParameterElementImpl> materializeStandalone(_Scope scope) {
createFragments();
createElements();
resolve(scope);
return [for (var declaration in _declarations) declaration.element];
}
void resolve(_Scope scope) {
for (var declaration in _declarations) {
declaration.resolve(scope);
}
}
}
/// Builds a set of libraries from a collection of [LibrarySpec]s.
///
/// The builder uses a two-pass process:
///
/// 1. Create fragments and elements for every declaration. This populates the
/// namespaces needed to resolve cross-library references.
/// 2. Resolve the parsed type representations in scope and write them into the
/// created fragments and elements.
///
/// This allows inter-library dependencies and cycles (e.g., `dart:core` and
/// `dart:async` referencing each other).
class _LibraryBuilder {
final RootReference rootReference;
final engine.AnalysisContext analysisContext;
final AnalysisSessionImpl analysisSession;
final Map<String, LibraryElementImpl> externalLibraries;
final Map<String, LibraryElementImpl> _libraryElements = {};
late final List<_LibraryDeclaration> _libraries;
_LibraryBuilder(
this.rootReference,
this.analysisContext,
this.analysisSession,
Map<String, LibrarySpec> specs,
this.externalLibraries,
) {
_libraries = [
for (var spec in specs.values) _LibraryDeclaration.fromSpec(spec),
];
}
Map<String, LibraryElementImpl> build() {
_createElements();
_resolveTypes();
return _libraryElements;
}
void _createElements() {
for (var library in _libraries) {
library.createElement(this);
_libraryElements[library.spec.uri] = library.element;
}
}
void _resolveTypes() {
for (var library in _libraries) {
library.resolveTypes(this);
}
}
_Scope _scopeFor(LibrarySpec spec) {
var libraries = <LibraryElementImpl>[];
for (var importUri in spec.imports) {
if (externalLibraries[importUri] case var externalLibrary?) {
libraries.add(externalLibrary);
}
if (_libraryElements[importUri] case var builtLibrary?) {
libraries.add(builtLibrary);
}
}
libraries.add(_libraryElements[spec.uri]!);
return _Scope.forLibraries(libraries: libraries);
}
}
class _LibraryDeclaration {
final LibrarySpec spec;
final List<_ClassDeclaration> classes;
final List<_EnumDeclaration> enums;
final List<_ExtensionTypeDeclaration> extensionTypes;
final List<_TopLevelFunctionDeclaration> functions;
final List<_MixinDeclaration> mixins;
final List<_TypeAliasDeclaration> typeAliases;
late final LibraryFragmentImpl fragment;
late final LibraryElementImpl element;
late final LibraryReference reference;
_LibraryDeclaration.fromSpec(this.spec)
: classes = [
for (var classSpec in spec.classes)
_ClassDeclaration.fromSpec(classSpec),
],
enums = [
for (var enumSpec in spec.enums) _EnumDeclaration.fromSpec(enumSpec),
],
extensionTypes = [
for (var extensionTypeSpec in spec.extensionTypes)
_ExtensionTypeDeclaration.fromSpec(extensionTypeSpec),
],
functions = [
for (var functionSpec in spec.functions)
_TopLevelFunctionDeclaration.fromSpec(functionSpec),
],
mixins = [
for (var mixinSpec in spec.mixins)
_MixinDeclaration.fromSpec(mixinSpec),
],
typeAliases = [
for (var typeAliasSpec in spec.typeAliases)
_TypeAliasDeclaration.fromSpec(typeAliasSpec),
];
void createElement(_LibraryBuilder builder) {
var libraryUriStr = spec.uri;
var librarySource = builder.analysisContext.sourceFactory.forUri(
libraryUriStr,
)!;
reference = builder.rootReference.getOrCreateLibrary(Uri.parse(spec.uri));
element = LibraryElementImpl(
builder.analysisContext,
builder.analysisSession,
libraryUriStr.replaceAll(':', '.'),
0,
0,
FeatureSet.latestLanguageVersion(),
);
element.reference = reference;
reference.element = element;
fragment = LibraryFragmentImpl(
library: element,
source: librarySource,
lineInfo: LineInfo([0]),
);
element.firstFragment = fragment;
for (var classDeclaration in classes) {
classDeclaration.createElement(libraryReference: reference);
fragment.addClass(classDeclaration.fragment);
element.addClass(classDeclaration.element);
}
for (var mixinDeclaration in mixins) {
mixinDeclaration.createElement(libraryReference: reference);
fragment.addMixin(mixinDeclaration.fragment);
element.addMixin(mixinDeclaration.element);
}
for (var enumDeclaration in enums) {
enumDeclaration.createElement(libraryReference: reference);
fragment.addEnum(enumDeclaration.fragment);
element.addEnum(enumDeclaration.element);
}
for (var extensionTypeDeclaration in extensionTypes) {
extensionTypeDeclaration.createElement(libraryReference: reference);
fragment.addExtensionType(extensionTypeDeclaration.fragment);
element.addExtensionType(extensionTypeDeclaration.element);
}
for (var typeAliasDeclaration in typeAliases) {
typeAliasDeclaration.createElement(libraryReference: reference);
fragment.addTypeAlias(typeAliasDeclaration.fragment);
element.addTypeAlias(typeAliasDeclaration.element);
}
for (var functionDeclaration in functions) {
functionDeclaration.createElement(libraryReference: reference);
fragment.addFunction(functionDeclaration.fragment);
element.addTopLevelFunction(functionDeclaration.element);
}
}
void resolveTypes(_LibraryBuilder builder) {
var libraryScope = builder._scopeFor(spec);
for (var typeAliasDeclaration in typeAliases) {
typeAliasDeclaration.resolve(libraryScope);
}
for (var extensionTypeDeclaration in extensionTypes) {
extensionTypeDeclaration.resolve(libraryScope);
}
for (var mixinDeclaration in mixins) {
mixinDeclaration.resolve(libraryScope);
}
for (var enumDeclaration in enums) {
enumDeclaration.resolve(libraryScope);
}
for (var classDeclaration in classes) {
classDeclaration.resolve(libraryScope);
}
for (var functionDeclaration in functions) {
functionDeclaration.resolve(libraryScope);
}
}
}
class _MethodDeclaration {
final MethodSpec spec;
final _ParsedExecutableHeader header;
final _FormalParameterDeclarations formalParameters;
final _TypeParameterDeclarations typeParameters;
late final MethodFragmentImpl fragment;
late final MethodElementImpl element;
factory _MethodDeclaration.fromSpec(MethodSpec spec) {
var header = _SpecParser.parseMethodHeader(spec.header);
return _MethodDeclaration._(spec, header);
}
_MethodDeclaration._(this.spec, this.header)
: formalParameters = _FormalParameterDeclarations(header.formalParameters),
typeParameters = _TypeParameterDeclarations(header.typeParameters);
void createElement({required ClassElementImpl classElement}) {
fragment = MethodFragmentImpl(name: header.name)
..typeParameters = typeParameters.createFragments()
..formalParameters = formalParameters.createFragments();
element = MethodElementImpl(
name: header.name,
reference: classElement.reference.declareMethod(header.name),
firstFragment: fragment,
);
formalParameters.createElements();
}
void resolve(_Scope classScope) {
var methodScope = _Scope.child(classScope);
typeParameters.resolve(methodScope);
formalParameters.resolve(methodScope);
element.returnType = header.returnType.materialize(methodScope);
}
}
class _MixinDeclaration {
final MixinSpec spec;
final _ParsedMixinHeader header;
final _TypeParameterDeclarations typeParameters;
late final MixinFragmentImpl fragment;
late final MixinElementImpl element;
factory _MixinDeclaration.fromSpec(MixinSpec spec) {
var header = _SpecParser.parseMixinHeader(spec.header);
return _MixinDeclaration._(spec, header);
}
_MixinDeclaration._(this.spec, this.header)
: typeParameters = _TypeParameterDeclarations(header.typeParameters);
void createElement({required LibraryReference libraryReference}) {
fragment = MixinFragmentImpl(name: header.name)
..typeParameters = typeParameters.createFragments();
element = MixinElementImpl(
libraryReference.declareMixin(header.name),
fragment,
);
}
void resolve(_Scope libraryScope) {
var scope = _Scope.child(libraryScope);
typeParameters.resolve(scope);
element.superclassConstraints = _materializeInterfaceTypes(
header.constraints,
scope,
);
element.interfaces = _materializeInterfaceTypes(header.interfaces, scope);
}
}
class _ParsedClassHeader {
final String name;
final bool isAbstract;
final bool isSealed;
final List<_ParsedTypeParameter> typeParameters;
final _ParsedType supertype;
final List<_ParsedType> mixins;
final List<_ParsedType> interfaces;
_ParsedClassHeader({
required this.name,
required this.isAbstract,
required this.isSealed,
required this.typeParameters,
required this.supertype,
required this.mixins,
required this.interfaces,
});
}
class _ParsedConstructorHeader {
final String name;
final bool isConst;
final bool isFactory;
final List<_ParsedFormalParameter> formalParameters;
_ParsedConstructorHeader({
required this.name,
required this.isConst,
required this.isFactory,
required this.formalParameters,
});
}
class _ParsedEnumHeader {
final String name;
final List<_ParsedType> mixins;
final List<_ParsedType> interfaces;
_ParsedEnumHeader({
required this.name,
required this.mixins,
required this.interfaces,
});
}
class _ParsedExecutableHeader {
final String name;
final List<_ParsedTypeParameter> typeParameters;
final List<_ParsedFormalParameter> formalParameters;
final _ParsedType returnType;
_ParsedExecutableHeader({
required this.name,
required this.typeParameters,
required this.formalParameters,
required this.returnType,
});
}
class _ParsedExplicitType implements _ParsedType {
final TypeImpl type;
_ParsedExplicitType({required this.type});
@override
TypeImpl materialize(_Scope scope) => type;
}
class _ParsedExtensionTypeHeader {
final String name;
final List<_ParsedTypeParameter> typeParameters;
final _ParsedType representationType;
final String representationName;
final List<_ParsedType> interfaces;
_ParsedExtensionTypeHeader({
required this.name,
required this.typeParameters,
required this.representationType,
required this.representationName,
required this.interfaces,
});
}
class _ParsedFormalParameter {
final bool isCovariant;
final ParameterKind kind;
final String? name;
final _ParsedType type;
_ParsedFormalParameter({
required this.isCovariant,
required this.kind,
required this.name,
required this.type,
});
}
class _ParsedFunctionType implements _ParsedType {
final List<_ParsedFormalParameter> formalParameters;
final _ParsedType returnType;
final List<_ParsedTypeParameter> typeParameters;
_ParsedFunctionType({
required this.typeParameters,
required this.formalParameters,
required this.returnType,
});
@override
TypeImpl materialize(_Scope scope) {
var functionScope = _Scope.child(scope);
var typeParameters = _TypeParameterDeclarations(this.typeParameters);
var typeParameterElements = typeParameters.materializeStandalone(
functionScope,
);
var formalParameters = _FormalParameterDeclarations(this.formalParameters);
var formalParameterElements = formalParameters.materializeStandalone(
functionScope,
);
return FunctionTypeImpl(
returnType: returnType.materialize(functionScope),
typeParameters: typeParameterElements,
formalParameters: formalParameterElements,
nullabilitySuffix: NullabilitySuffix.none,
);
}
}
class _ParsedMixinHeader {
final String name;
final List<_ParsedTypeParameter> typeParameters;
final List<_ParsedType> constraints;
final List<_ParsedType> interfaces;
_ParsedMixinHeader({
required this.name,
required this.typeParameters,
required this.constraints,
required this.interfaces,
});
}
class _ParsedNamedType implements _ParsedType {
final List<_ParsedType> args;
final String name;
_ParsedNamedType({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,
);
}
if (scope.lookupTypeAlias(name) case var element?) {
return element.instantiateImpl(
typeArguments: args.map((arg) => arg.materialize(scope)).toList(),
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,
);
}
}
class _ParsedNullableType implements _ParsedType {
final _ParsedType inner;
_ParsedNullableType({required this.inner});
@override
TypeImpl materialize(_Scope scope) {
return inner.materialize(scope).withNullability(NullabilitySuffix.question);
}
}
class _ParsedPromotedType implements _ParsedType {
final _ParsedType base;
final _ParsedType promotedBound;
_ParsedPromotedType({required this.base, required this.promotedBound});
@override
TypeImpl materialize(_Scope scope) {
var baseType = base.materialize(scope);
if (baseType is! TypeParameterTypeImpl) {
throw StateError('Cannot promote a non-type-parameter type: $baseType');
}
return TypeParameterTypeImpl(
element: baseType.element,
nullabilitySuffix: baseType.nullabilitySuffix,
promotedBound: promotedBound.materialize(scope),
);
}
}
class _ParsedRecordType implements _ParsedType {
final List<({String name, _ParsedType type})> namedFields;
final List<_ParsedType> positionalFields;
_ParsedRecordType({
required this.positionalFields,
required this.namedFields,
});
@override
TypeImpl materialize(_Scope scope) {
return RecordTypeImpl(
positionalFields: [
for (var type in positionalFields)
RecordTypePositionalFieldImpl(type: type.materialize(scope)),
],
namedFields: [
for (var field in namedFields)
RecordTypeNamedFieldImpl(
name: field.name,
type: field.type.materialize(scope),
),
],
nullabilitySuffix: NullabilitySuffix.none,
);
}
}
/// Representation of a [TypeImpl] that has been parsed but hasn't had meaning
/// assigned to its identifiers yet.
abstract class _ParsedType {
/// 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 _ParsedTypeAliasHeader {
final String name;
final List<_ParsedTypeParameter> typeParameters;
final _ParsedType aliasedType;
_ParsedTypeAliasHeader({
required this.name,
required this.typeParameters,
required this.aliasedType,
});
}
class _ParsedTypeParameter {
final _ParsedType? bound;
final String name;
final Variance? variance;
_ParsedTypeParameter({
required this.name,
required this.bound,
required this.variance,
}) : 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;
final Map<String, TypeAliasElementImpl> _typeAliases;
/// 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,
_typeAliases = parent._typeAliases;
factory _Scope.forLibraries({
required List<LibraryElementImpl> libraries,
List<TypeParameterElementImpl> typeParameters = const [],
}) {
var interfaces = <String, InterfaceElementImpl>{};
var typeAliases = <String, TypeAliasElementImpl>{};
for (var library in libraries) {
for (var element in library.classes) {
if (element.name case var name?) {
interfaces[name] = element;
}
}
for (var element in library.enums) {
if (element.name case var name?) {
interfaces[name] = element;
}
}
for (var element in library.extensionTypes) {
if (element.name case var name?) {
interfaces[name] = element;
}
}
for (var element in library.mixins) {
if (element.name case var name?) {
interfaces[name] = element;
}
}
for (var element in library.typeAliases) {
if (element.name case var name?) {
typeAliases[name] = element;
}
}
}
var scope = _Scope.root(interfaces: interfaces, typeAliases: typeAliases);
for (var typeParameter in typeParameters) {
scope.addTypeParameter(typeParameter);
}
return scope;
}
/// Creates a root scope containing all known interfaces.
_Scope.root({
required Map<String, InterfaceElementImpl> interfaces,
required Map<String, TypeAliasElementImpl> typeAliases,
}) : parent = null,
_interfaces = interfaces,
_typeAliases = typeAliases;
/// 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];
}
TypeAliasElementImpl? lookupTypeAlias(String name) {
return _typeAliases[name];
}
/// Looks up a type parameter by name, walking up the scope chain.
TypeParameterElementImpl? lookupTypeParameter(String name) {
return _typeParameters[name] ?? parent?.lookupTypeParameter(name);
}
}
class _SpecParser {
final _TokenStream _stream;
_SpecParser._(String input) : _stream = _TokenStream.fromString(input);
void _expectEnd(String message) {
if (!_stream.isAtEnd) {
throw StateError(message);
}
}
_ParsedClassHeader _parseClassHeader() {
var isAbstract = false;
var isSealed = false;
while (!_stream.isAtEnd && !_stream.peekIs('class')) {
switch (_stream.consume()) {
case 'abstract':
isAbstract = true;
case 'sealed':
isSealed = true;
case var modifier:
throw StateError('Unsupported class modifier: $modifier');
}
}
_stream.expect('class');
var name = _stream.consume();
var typeParameters = _parseOptionalTypeParameters(
context: _TypeParameterContext.classDeclaration,
);
_ParsedType supertype;
if (_stream.match('extends')) {
supertype = _parseType();
} else {
supertype = _ParsedNamedType(name: 'Object', args: <_ParsedType>[]);
}
var mixins = <_ParsedType>[];
if (_stream.match('with')) {
mixins = _parseTypes(stopTokens: const {'implements'});
}
var interfaces = <_ParsedType>[];
if (_stream.match('implements')) {
interfaces = _parseTypes();
}
_expectEnd('Unexpected trailing tokens in class header.');
return _ParsedClassHeader(
name: name,
isAbstract: isAbstract,
isSealed: isSealed,
typeParameters: typeParameters,
supertype: supertype,
mixins: mixins,
interfaces: interfaces,
);
}
_ParsedConstructorHeader _parseConstructorHeader() {
var isConst = _stream.match('const');
var isFactory = _stream.match('factory');
var name = _stream.consume();
_stream.expect('(');
var formalParameters = _parseFormalParameters();
_stream.expect(')');
_expectEnd('Unexpected trailing tokens in constructor header.');
return _ParsedConstructorHeader(
name: name,
isConst: isConst,
isFactory: isFactory,
formalParameters: formalParameters,
);
}
_ParsedEnumHeader _parseEnumHeader() {
_stream.expect('enum');
var name = _stream.consume();
var mixins = <_ParsedType>[];
if (_stream.match('with')) {
mixins = _parseTypes(stopTokens: const {'implements'});
}
var interfaces = <_ParsedType>[];
if (_stream.match('implements')) {
interfaces = _parseTypes();
}
_stream.match(';');
_expectEnd('Unexpected trailing tokens in enum header.');
return _ParsedEnumHeader(
name: name,
mixins: mixins,
interfaces: interfaces,
);
}
_ParsedExecutableHeader _parseExecutableHeader(
_ExecutableHeaderContext context,
) {
var returnType = _parseType();
var name = _parseExecutableName(context);
var typeParameters = _parseOptionalTypeParameters(
context: context.typeParameterContext,
);
_stream.expect('(');
var formalParameters = _parseFormalParameters();
_stream.expect(')');
_stream.match(';');
_expectEnd('Unexpected trailing tokens in ${context.name} header.');
return _ParsedExecutableHeader(
name: name,
typeParameters: typeParameters,
formalParameters: formalParameters,
returnType: returnType,
);
}
String _parseExecutableName(_ExecutableHeaderContext context) {
if (context.allowsOperators && _stream.match('operator')) {
return _parseOperatorName();
}
return _stream.consume();
}
_ParsedExtensionTypeHeader _parseExtensionTypeHeader() {
_stream.expect('extension');
_stream.expect('type');
var name = _stream.consume();
var typeParameters = _parseOptionalTypeParameters(
context: _TypeParameterContext.extensionTypeDeclaration,
);
_stream.expect('(');
var representationType = _parseType();
var representationName = _stream.consume();
_stream.expect(')');
var interfaces = <_ParsedType>[];
if (_stream.match('implements')) {
interfaces = _parseTypes();
}
_expectEnd('Unexpected trailing tokens in extension type header.');
return _ParsedExtensionTypeHeader(
name: name,
typeParameters: typeParameters,
representationType: representationType,
representationName: representationName,
interfaces: interfaces,
);
}
_ParsedFormalParameter _parseFormalParameter(ParameterKind kind) {
if (kind == ParameterKind.NAMED && _stream.match('required')) {
kind = ParameterKind.NAMED_REQUIRED;
}
var isCovariant = _stream.match('covariant');
var type = _parseType();
String? name;
if (!_stream.isAtEnd && !_stream.peekIsAnyOf(const {',', ')', ']', '}'})) {
name = _stream.consume();
}
return _ParsedFormalParameter(
isCovariant: isCovariant,
type: type,
name: name,
kind: kind,
);
}
List<_ParsedFormalParameter> _parseFormalParameters() {
var formalParameters = <_ParsedFormalParameter>[];
if (_stream.isAtEnd || _stream.peekIs(')')) {
return formalParameters;
}
while (!_stream.isAtEnd && !_stream.peekIsAnyOf(const {')', '[', '{'})) {
formalParameters.add(_parseFormalParameter(ParameterKind.REQUIRED));
_stream.match(',');
}
if (_stream.match('[')) {
while (!_stream.isAtEnd && !_stream.peekIs(']')) {
formalParameters.add(_parseFormalParameter(ParameterKind.POSITIONAL));
_stream.match(',');
}
_stream.expect(']');
}
if (_stream.match('{')) {
while (!_stream.isAtEnd && !_stream.peekIs('}')) {
formalParameters.add(_parseFormalParameter(ParameterKind.NAMED));
_stream.match(',');
}
_stream.expect('}');
}
return formalParameters;
}
_ParsedMixinHeader _parseMixinHeader() {
_stream.expect('mixin');
var name = _stream.consume();
var typeParameters = _parseOptionalTypeParameters(
context: _TypeParameterContext.mixinDeclaration,
);
var constraints = <_ParsedType>[
_ParsedNamedType(name: 'Object', args: <_ParsedType>[]),
];
if (_stream.match('on')) {
constraints = _parseTypes(stopTokens: const {'implements'});
}
var interfaces = <_ParsedType>[];
if (_stream.match('implements')) {
interfaces = _parseTypes();
}
_expectEnd('Unexpected trailing tokens in mixin header.');
return _ParsedMixinHeader(
name: name,
typeParameters: typeParameters,
constraints: constraints,
interfaces: interfaces,
);
}
String _parseOperatorName() {
if (_stream.match('[')) {
_stream.expect(']');
if (_stream.match('=')) {
return '[]=';
}
return '[]';
}
if (_stream.peekIsAnyOf(const {
'+',
'-',
'*',
'/',
'%',
'~',
'&',
'|',
'^',
})) {
return _stream.consume();
}
if (_stream.match('=')) {
_stream.expect('=');
return '==';
}
if (_stream.match('<')) {
if (_stream.match('=')) {
return '<=';
}
if (_stream.match('<')) {
return '<<';
}
return '<';
}
if (_stream.match('>')) {
if (_stream.match('=')) {
return '>=';
}
if (_stream.match('>')) {
if (_stream.match('>')) {
return '>>>';
}
return '>>';
}
return '>';
}
throw StateError('Unsupported operator token in executable header.');
}
List<_ParsedTypeParameter> _parseOptionalTypeParameters({
required _TypeParameterContext context,
}) {
if (_stream.isAtEnd || !_stream.match('<')) {
return const [];
}
return _parseTypeParametersRest(context: context);
}
_ParsedType _parseParenthesizedOrRecordType() {
_stream.expect('(');
var positionalFields = <_ParsedType>[];
var namedFields = <({String name, _ParsedType type})>[];
var hasComma = false;
while (!_stream.isAtEnd && !_stream.peekIsAnyOf(const {')', '{'})) {
positionalFields.add(_parseType());
hasComma = _stream.match(',');
if (!hasComma) {
break;
}
}
if (_stream.match('{')) {
while (!_stream.isAtEnd && !_stream.peekIs('}')) {
var type = _parseType();
var name = _stream.consume();
namedFields.add((name: name, type: type));
_stream.match(',');
}
_stream.expect('}');
}
_stream.expect(')');
// If there is exactly one positional field, no trailing comma, and no
// named fields, it is a parenthesized type, not a record type.
// Example: `(int)` is parenthesized, but `(int,)` is a record.
if (positionalFields.length == 1 && !hasComma && namedFields.isEmpty) {
return positionalFields[0];
}
return _ParsedRecordType(
positionalFields: positionalFields,
namedFields: namedFields,
);
}
_ParsedType _parsePrimaryType() {
if (_stream.peekIs('(')) {
return _parseParenthesizedOrRecordType();
}
var name = _stream.consume();
switch (name) {
case 'dynamic':
return _ParsedExplicitType(type: DynamicTypeImpl.instance);
case 'InvalidType':
return _ParsedExplicitType(type: InvalidTypeImpl.instance);
case 'Never':
return _ParsedExplicitType(type: NeverTypeImpl.instance);
case 'UnknownInferredType':
return _ParsedExplicitType(type: UnknownInferredType.instance);
case 'void':
return _ParsedExplicitType(type: VoidTypeImpl.instance);
}
var args = <_ParsedType>[];
if (_stream.match('<')) {
while (!_stream.isAtEnd && !_stream.peekIs('>')) {
args.add(_parseType());
_stream.match(',');
}
_stream.expect('>');
}
return _ParsedNamedType(name: name, args: args);
}
_ParsedType _parseType() {
var type = _parsePrimaryType();
while (true) {
if (_stream.match('?')) {
type = _ParsedNullableType(inner: type);
continue;
}
if (_stream.match('Function')) {
var typeParameters = _parseOptionalTypeParameters(
context: _TypeParameterContext.genericFunctionType,
);
_stream.expect('(');
var formalParameters = _parseFormalParameters();
_stream.expect(')');
type = _ParsedFunctionType(
returnType: type,
formalParameters: formalParameters,
typeParameters: typeParameters,
);
continue;
}
if (_stream.match('&')) {
type = _ParsedPromotedType(base: type, promotedBound: _parseType());
continue;
}
return type;
}
}
_ParsedTypeAliasHeader _parseTypeAliasHeader() {
_stream.expect('typedef');
var name = _stream.consume();
var typeParameters = _parseOptionalTypeParameters(
context: _TypeParameterContext.typeAliasDeclaration,
);
_stream.expect('=');
var aliasedType = _parseType();
_stream.match(';');
_expectEnd('Unexpected trailing tokens in type alias header.');
return _ParsedTypeAliasHeader(
name: name,
typeParameters: typeParameters,
aliasedType: aliasedType,
);
}
List<_ParsedTypeParameter> _parseTypeParametersRest({
required _TypeParameterContext context,
}) {
if (_stream.peekIs('>')) {
throw StateError('Type parameter clause cannot be empty.');
}
var typeParameters = <_ParsedTypeParameter>[];
while (!_stream.isAtEnd && !_stream.peekIs('>')) {
Variance? variance;
if (_varianceForKeyword(_stream.peek()) case var keywordVariance?) {
_stream.consume();
if (!context.allowsVariance) {
throw StateError(
'Variance modifiers are not allowed in ${context.name}.',
);
}
variance = keywordVariance;
}
var name = _stream.consume();
_ParsedType? bound;
if (_stream.match('extends')) {
bound = _parseType();
}
typeParameters.add(
_ParsedTypeParameter(name: name, bound: bound, variance: variance),
);
_stream.match(',');
}
_stream.expect('>');
return typeParameters;
}
List<_ParsedType> _parseTypes({Set<String> stopTokens = const {}}) {
if (_stream.isAtEnd || _stream.peekIsAnyOf(stopTokens)) {
throw StateError('Expected a type.');
}
var types = <_ParsedType>[];
while (!_stream.isAtEnd && !_stream.peekIsAnyOf(stopTokens)) {
types.add(_parseType());
if (!_stream.match(',')) {
break;
}
if (_stream.isAtEnd || _stream.peekIsAnyOf(stopTokens)) {
throw StateError('Expected a type after ",".');
}
}
return types;
}
static _ParsedClassHeader parseClassHeader(String input) {
return _SpecParser._(input)._parseClassHeader();
}
static _ParsedConstructorHeader parseConstructorHeader(String input) {
return _SpecParser._(input)._parseConstructorHeader();
}
static _ParsedEnumHeader parseEnumHeader(String input) {
return _SpecParser._(input)._parseEnumHeader();
}
static _ParsedExtensionTypeHeader parseExtensionTypeHeader(String input) {
return _SpecParser._(input)._parseExtensionTypeHeader();
}
static _ParsedExecutableHeader parseMethodHeader(String input) {
return _SpecParser._(
input,
)._parseExecutableHeader(_ExecutableHeaderContext.method);
}
static _ParsedMixinHeader parseMixinHeader(String input) {
return _SpecParser._(input)._parseMixinHeader();
}
static _ParsedExecutableHeader parseTopLevelFunctionHeader(String input) {
return _SpecParser._(
input,
)._parseExecutableHeader(_ExecutableHeaderContext.topLevelFunction);
}
static _ParsedType parseType(String input) {
var parser = _SpecParser._(input);
var type = parser._parseType();
parser._expectEnd('Unexpected trailing tokens in type.');
return type;
}
static _ParsedTypeAliasHeader parseTypeAliasHeader(String input) {
return _SpecParser._(input)._parseTypeAliasHeader();
}
static List<_ParsedTypeParameter> parseTypeParameters(String input) {
input = input.trim();
if (!input.startsWith('<')) {
input = '<$input>';
}
var parser = _SpecParser._(input);
parser._stream.expect('<');
var typeParameters = parser._parseTypeParametersRest(
context: _TypeParameterContext.classDeclaration,
);
parser._expectEnd('Unexpected trailing tokens in type parameters.');
return typeParameters;
}
static Variance? _varianceForKeyword(String keyword) {
switch (keyword) {
case 'out':
return Variance.covariant;
case 'in':
return Variance.contravariant;
case 'inout':
return Variance.invariant;
}
return null;
}
}
class _TokenStream {
static final RegExp _tokenizer = RegExp(
r'[$a-zA-Z_][$\w]*|<|>|\+|-|\*|/|%|~|&|\||\^|,|\?|\(|\)|\{|\}|\[|\]|=|;',
);
final List<String> _tokens;
int _index = 0;
factory _TokenStream.fromString(String input) {
var tokens = _tokenizer.allMatches(input).map((m) => m[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 _TopLevelFunctionDeclaration {
final TopLevelFunctionSpec spec;
final _ParsedExecutableHeader header;
final _FormalParameterDeclarations formalParameters;
final _TypeParameterDeclarations typeParameters;
late final TopLevelFunctionFragmentImpl fragment;
late final TopLevelFunctionElementImpl element;
factory _TopLevelFunctionDeclaration.fromSpec(TopLevelFunctionSpec spec) {
var header = _SpecParser.parseTopLevelFunctionHeader(spec.header);
return _TopLevelFunctionDeclaration._(spec, header);
}
_TopLevelFunctionDeclaration._(this.spec, this.header)
: formalParameters = _FormalParameterDeclarations(header.formalParameters),
typeParameters = _TypeParameterDeclarations(header.typeParameters);
void createElement({required LibraryReference libraryReference}) {
fragment = TopLevelFunctionFragmentImpl(name: header.name)
..typeParameters = typeParameters.createFragments()
..formalParameters = formalParameters.createFragments();
element = TopLevelFunctionElementImpl(
libraryReference.declareTopLevelFunction(header.name),
fragment,
);
formalParameters.createElements();
}
void resolve(_Scope libraryScope) {
var functionScope = _Scope.child(libraryScope);
typeParameters.resolve(functionScope);
formalParameters.resolve(functionScope);
element.returnType = header.returnType.materialize(functionScope);
}
}
class _TypeAliasDeclaration {
final TypeAliasSpec spec;
final _ParsedTypeAliasHeader header;
final _TypeParameterDeclarations typeParameters;
late final TypeAliasFragmentImpl fragment;
late final TypeAliasElementImpl element;
factory _TypeAliasDeclaration.fromSpec(TypeAliasSpec spec) {
var header = _SpecParser.parseTypeAliasHeader(spec.header);
return _TypeAliasDeclaration._(spec, header);
}
_TypeAliasDeclaration._(this.spec, this.header)
: typeParameters = _TypeParameterDeclarations(header.typeParameters);
void createElement({required LibraryReference libraryReference}) {
fragment = TypeAliasFragmentImpl(name: header.name, firstTokenOffset: null)
..typeParameters = typeParameters.createFragments();
element = TypeAliasElementImpl(
libraryReference.declareTypeAlias(header.name),
fragment,
);
}
void resolve(_Scope libraryScope) {
var scope = _Scope.child(libraryScope);
typeParameters.resolve(scope);
element.aliasedType = header.aliasedType.materialize(scope);
}
}
enum _TypeParameterContext {
classDeclaration(allowsVariance: true),
extensionTypeDeclaration(allowsVariance: true),
genericFunctionType(allowsVariance: true),
methodDeclaration(allowsVariance: false),
mixinDeclaration(allowsVariance: true),
topLevelFunctionDeclaration(allowsVariance: false),
typeAliasDeclaration(allowsVariance: true);
final bool allowsVariance;
const _TypeParameterContext({required this.allowsVariance});
}
class _TypeParameterDeclaration {
final _ParsedTypeParameter parsed;
late final TypeParameterFragmentImpl fragment;
_TypeParameterDeclaration(this.parsed);
TypeParameterElementImpl get element => fragment.element;
void createElement() {
TypeParameterElementImpl(firstFragment: fragment);
}
TypeParameterFragmentImpl createFragment() {
return fragment = TypeParameterFragmentImpl(name: parsed.name);
}
void resolve(_Scope scope) {
if (parsed.bound case var bound?) {
element.bound = bound.materialize(scope);
}
if (parsed.variance case var variance?) {
element.variance = variance;
}
}
}
class _TypeParameterDeclarations {
final List<_TypeParameterDeclaration> _declarations;
_TypeParameterDeclarations(Iterable<_ParsedTypeParameter> parameters)
: _declarations = [
for (var parameter in parameters) _TypeParameterDeclaration(parameter),
];
List<TypeParameterElementImpl> get elements => [
for (var declaration in _declarations) declaration.element,
];
void createElements() {
for (var declaration in _declarations) {
declaration.createElement();
}
}
List<TypeParameterFragmentImpl> createFragments() {
return [
for (var declaration in _declarations) declaration.createFragment(),
];
}
List<TypeParameterElementImpl> materializeStandalone(_Scope scope) {
createFragments();
createElements();
resolve(scope);
return elements;
}
void resolve(_Scope scope) {
for (var declaration in _declarations) {
scope.addTypeParameter(declaration.element);
}
for (var declaration in _declarations) {
declaration.resolve(scope);
}
}
}