blob: 6eb1acd00528cc9c7b5b29a916610e48d95dae5f [file] [log] [blame]
// 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/fasta/kernel/constructor_tearoff_lowering.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<VariableDeclaration, 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) {
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 visitVariableDeclaration(VariableDeclaration 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(
VariableDeclaration node, VariableSymbolKind kind) =>
VariableSymbol(
name: node.name!,
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));
}