| // Copyright (c) 2021, 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:front_end/src/api_prototype/lowering_predicates.dart' | |
| show isTearOffLowering; | |
| import 'package:kernel/kernel.dart'; | |
| import 'module_symbols.dart'; | |
| class ModuleSymbolsCollector extends RecursiveVisitor { | |
| /// Stack of active scopes while visiting the original Dart program. | |
| /// | |
| /// The first scope added to the stack should always be the library scope. The | |
| /// last element in the list represents the current scope. | |
| final _scopes = <ScopeSymbol>[]; | |
| final ModuleSymbols _moduleSymbols; | |
| final Map<Class, String> _classJsNames; | |
| final Map<Member, String> _memberJsNames; | |
| final Map<Procedure, String> _procedureJsNames; | |
| final Map<Variable, String> _variableJsNames; | |
| ModuleSymbolsCollector( | |
| String moduleName, | |
| this._classJsNames, | |
| this._memberJsNames, | |
| this._procedureJsNames, | |
| this._variableJsNames, | |
| ) : _moduleSymbols = ModuleSymbols(moduleName: moduleName); | |
| ModuleSymbols collectSymbolInfo(Component node) { | |
| node.accept(this); | |
| return _moduleSymbols; | |
| } | |
| /// Returns the id of the script in this module with the matching [fileUri]. | |
| String _scriptId(Uri fileUri) => fileUri.toString(); | |
| /// Returns the id of [type]. | |
| // TODO(nshahan) Only nullable until we design how to identify types from | |
| // other modules. | |
| String? _typeId(DartType type) => | |
| // TODO(nshahan) How to handle function types or types from other modules? | |
| type is InterfaceType ? _classJsNames[type.classNode] : null; | |
| /// Returns the symbol for the function defined by [node]. | |
| void _createFunctionSymbol(Member node) { | |
| var functionSymbol = FunctionSymbol( | |
| name: node.name.text, | |
| // TODO(nshahan) typeId - probably should canonicalize but keep original | |
| // type argument names. | |
| typeId: null, | |
| // TODO(nshahan) Should we mark all constructors static? | |
| isStatic: node is Procedure ? node.isStatic : false, | |
| isConst: node.isConst, | |
| localId: _memberJsNames[node] ?? _procedureJsNames[node]!, | |
| scopeId: _scopes.last.id, | |
| location: SourceLocation( | |
| scriptId: _scriptId(node.location!.file), | |
| tokenPos: node.fileOffset, | |
| endTokenPos: node.fileEndOffset, | |
| ), | |
| ); | |
| _scopes.add(functionSymbol); | |
| node.visitChildren(this); | |
| _scopes | |
| ..removeLast() | |
| ..last.scopeIds.add(functionSymbol.id); | |
| _moduleSymbols.functions.add(functionSymbol); | |
| } | |
| @override | |
| void visitClass(Class node) { | |
| // Some class names are not emitted, i.e. mixin applications. | |
| if (_classJsNames[node] != null) { | |
| var classSymbol = ClassSymbol( | |
| name: node.name, | |
| isAbstract: node.isAbstract, | |
| isConst: node.constructors.any((constructor) => constructor.isConst), | |
| superClassId: _classJsNames[node.superclass], | |
| interfaceIds: [ | |
| for (var type in node.implementedTypes) | |
| _classJsNames[type.classNode]!, | |
| ], | |
| typeParameters: { | |
| for (var param in node.typeParameters) | |
| // TODO(nshahan) Value should be the JS name. | |
| param.name!: param.name!, | |
| }, | |
| localId: _classJsNames[node]!, | |
| scopeId: _scopes.last.id, | |
| location: SourceLocation( | |
| scriptId: _scriptId(node.location!.file), | |
| tokenPos: node.startFileOffset, | |
| endTokenPos: node.fileEndOffset, | |
| ), | |
| ); | |
| _scopes.add(classSymbol); | |
| node.visitChildren(this); | |
| _scopes | |
| ..removeLast() | |
| ..last.scopeIds.add(classSymbol.id); | |
| _moduleSymbols.classes.add(classSymbol); | |
| } | |
| } | |
| @override | |
| void visitConstructor(Constructor node) => _createFunctionSymbol(node); | |
| @override | |
| void visitField(Field node) { | |
| var fieldSymbol = VariableSymbol( | |
| name: node.name.text, | |
| kind: node.parent is Class | |
| ? VariableSymbolKind.field | |
| : VariableSymbolKind.global, | |
| isConst: node.isConst, | |
| isFinal: node.isFinal, | |
| isStatic: node.isStatic, | |
| typeId: _typeId(node.type), | |
| localId: _memberJsNames[node]!, | |
| scopeId: _scopes.last.id, | |
| location: SourceLocation( | |
| scriptId: _scriptId(node.location!.file), | |
| tokenPos: node.fileOffset, | |
| endTokenPos: node.fileEndOffset, | |
| ), | |
| ); | |
| node.visitChildren(this); | |
| _scopes.last.variableIds.add(fieldSymbol.id); | |
| _moduleSymbols.variables.add(fieldSymbol); | |
| } | |
| @override | |
| void visitLibrary(Library node) { | |
| var librarySymbol = LibrarySymbol( | |
| name: node.name, | |
| uri: node.importUri.toString(), | |
| dependencies: [ | |
| for (var dep in node.dependencies) | |
| LibrarySymbolDependency( | |
| isImport: dep.isImport, | |
| isDeferred: dep.isDeferred, | |
| // TODO(nshahan) Need to handle prefixes. | |
| targetId: dep.targetLibrary.importUri.toString(), | |
| ), | |
| ], | |
| scriptIds: [], | |
| ); | |
| // TODO(nshahan) Save some space by using integers as local ids? | |
| var scripts = [ | |
| Script( | |
| uri: node.fileUri.toString(), | |
| localId: _scriptId(node.fileUri), | |
| libraryId: librarySymbol.id, | |
| ), | |
| for (var part in node.parts) | |
| Script( | |
| uri: node.fileUri.resolve(part.partUri).toString(), | |
| localId: _scriptId(node.fileUri.resolve(part.partUri)), | |
| libraryId: librarySymbol.id, | |
| ), | |
| ]; | |
| librarySymbol.scriptIds.addAll(scripts.map((s) => s.id)); | |
| _moduleSymbols.scripts.addAll(scripts); | |
| _scopes.add(librarySymbol); | |
| node.visitChildren(this); | |
| _scopes.removeLast(); | |
| _moduleSymbols.libraries.add(librarySymbol); | |
| } | |
| @override | |
| void visitProcedure(Procedure node) { | |
| // Legacy libraries contain procedures with no bodies for all Object methods | |
| // in every class. We can ignore these unless they actually contain a body. | |
| // | |
| // Also avoid adding information for the static methods introduced by the | |
| // CFE lowering for constructor tearoffs. | |
| if (node.function.body == null || isTearOffLowering(node)) return; | |
| _createFunctionSymbol(node); | |
| } | |
| @override | |
| void defaultVariable(Variable node) { | |
| var kind = node.isInitializingFormal | |
| ? VariableSymbolKind.formal | |
| : VariableSymbolKind.local; | |
| var variableSymbol = _createVariableSymbol(node, kind); | |
| node.visitChildren(this); | |
| _scopes.last.variableIds.add(variableSymbol.id); | |
| _moduleSymbols.variables.add(variableSymbol); | |
| } | |
| VariableSymbol _createVariableSymbol( | |
| Variable node, | |
| VariableSymbolKind kind, | |
| ) => VariableSymbol( | |
| name: node.cosmeticName!, | |
| kind: kind, | |
| isConst: node.isConst, | |
| isFinal: node.isFinal, | |
| // Static fields are visited in `visitField()`. | |
| isStatic: false, | |
| typeId: _typeId(node.type), | |
| localId: _variableJsNames[node]!, | |
| scopeId: _scopes.last.id, | |
| location: SourceLocation( | |
| scriptId: _scriptId(node.location!.file), | |
| tokenPos: node.fileOffset, | |
| ), | |
| ); | |
| } |