| // 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); | |
| } | |
| } | |
| } |