blob: 9340cafa497c04f8a1076e703bb9cea3c8c9db6e [file] [log] [blame]
import 'dart:async';
import 'dart:convert';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/kernel_metadata.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:front_end/src/api_prototype/byte_store.dart';
import 'package:front_end/src/api_prototype/compilation_message.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/file_system.dart' as front_end;
import 'package:front_end/src/base/libraries_specification.dart';
import 'package:front_end/src/base/performance_logger.dart';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/fasta/builder/builder.dart';
import 'package:front_end/src/fasta/builder/library_builder.dart';
import 'package:front_end/src/fasta/compiler_context.dart';
import 'package:front_end/src/fasta/dill/dill_target.dart';
import 'package:front_end/src/fasta/kernel/kernel_target.dart';
import 'package:front_end/src/fasta/kernel/metadata_collector.dart';
import 'package:front_end/src/fasta/source/diet_listener.dart';
import 'package:front_end/src/fasta/source/source_library_builder.dart';
import 'package:front_end/src/fasta/source/source_loader.dart';
import 'package:front_end/src/fasta/source/stack_listener.dart';
import 'package:front_end/src/fasta/target_implementation.dart';
import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart';
import 'package:front_end/src/fasta/uri_translator.dart';
import 'package:front_end/src/fasta/uri_translator_impl.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/target/targets.dart';
import 'package:kernel/type_environment.dart';
import 'package:package_config/packages.dart';
import 'package:package_config/src/packages_impl.dart';
import 'package:path/path.dart' as pathos;
/// Resolution information in a single function body.
class CollectedResolution {
/// The list of local declarations stored by body builders while
/// compiling the library.
final List<TreeNode> kernelDeclarations = [];
/// The list of references to local or external stored by body builders
/// while compiling the library.
final List<Node> kernelReferences = [];
/// The list of types stored by body builders while compiling the library.
final List<DartType> kernelTypes = [];
/// File offsets corresponding to the declarations in [kernelDeclarations].
///
/// These are used strictly for validation purposes.
final List<int> declarationOffsets = [];
/// File offsets corresponding to the objects in [kernelReferences].
///
/// These are used strictly for validation purposes.
final List<int> referenceOffsets = [];
/// File offsets corresponding to the types in [kernelTypes].
///
/// These are used strictly for validation purposes.
final List<int> typeOffsets = [];
}
/// The compilation result for a single file.
class FileCompilationResult {
/// The file system URI of the file.
final Uri fileUri;
/// The list of resolution for each code block, e.g function body.
final List<CollectedResolution> resolutions;
/// The list of all FrontEnd errors in the file.
final List<CompilationMessage> errors;
FileCompilationResult(this.fileUri, this.resolutions, this.errors);
}
/// The wrapper around FrontEnd compiler that can be used incrementally.
///
/// When the client needs the kernel, resolution information, and errors for
/// a library, it should call [compile]. The compiler will compile the library
/// and the transitive closure of its dependencies. The results are cached,
/// so the next invocation for a dependency will be served from the cache.
///
/// If a file is changed, [invalidate] should be invoked. This will invalidate
/// the file, its library, and the transitive closure of dependencies. So, the
/// next invocation of [compile] will recompile libraries required for the
/// requested library.
class FrontEndCompiler {
static const MSG_PENDING_COMPILE =
'A compile() invocation is still executing.';
/// Options used by the kernel compiler.
final ProcessedOptions _options;
/// The logger to report compilation progress.
final PerformanceLog _logger;
/// The [FileSystem] to access file during compilation.
final front_end.FileSystem _fileSystem;
/// The object that knows how to resolve "package:" and "dart:" URIs.
final UriTranslator uriTranslator;
/// The listener / recorder for compilation errors produced by the compiler.
final _ErrorListener _errorListener;
/// Each key is the absolute URI of a library.
/// Each value is the compilation result of the key library.
final Map<Uri, LibraryCompilationResult> _results = {};
/// The [Component] with currently valid libraries. When a file is invalidated,
/// we remove the file, its library, and everything affected from [_component].
Component _component = new Component();
/// Each key is the file system URI of a library.
/// Each value is the libraries that directly depend on the key library.
final Map<Uri, Set<Uri>> _directLibraryDependencies = {};
/// Each key is the file system URI of a library.
/// Each value is the [Library] that is still in the [_component].
final Map<Uri, Library> _uriToLibrary = {};
/// Each key is the file system URI of a part.
/// Each value is the file system URI of the library that sources the part.
final Map<Uri, Uri> _partToLibrary = {};
/// Whether [compile] is executing.
bool _isCompileExecuting = false;
factory FrontEndCompiler(
PerformanceLog logger,
ByteStore byteStore,
AnalysisOptions analysisOptions,
Folder sdkFolder,
SourceFactory sourceFactory,
FileSystemState fsState,
pathos.Context pathContext) {
// Prepare SDK libraries.
Map<String, LibraryInfo> dartLibraries = {};
{
DartSdk dartSdk = sourceFactory.dartSdk;
dartSdk.sdkLibraries.forEach((sdkLibrary) {
var dartUri = sdkLibrary.shortName;
var name = Uri.parse(dartUri).path;
var path = dartSdk.mapDartUri(dartUri).fullName;
var fileUri = pathContext.toUri(path);
dartLibraries[name] = new LibraryInfo(name, fileUri, const []);
});
}
// Prepare packages.
Packages packages = Packages.noPackages;
{
Map<String, List<Folder>> packageMap = sourceFactory.packageMap;
if (packageMap != null) {
var map = <String, Uri>{};
for (var name in packageMap.keys) {
map[name] = packageMap[name].first.toUri();
}
packages = new MapPackages(map);
}
}
// TODO(scheglov) Should we restore this?
// // Try to find the SDK outline.
// // It is not used for unit testing, we compile SDK sources.
// // But for running shared tests we need the patched SDK.
// List<int> sdkOutlineBytes;
// if (sdkFolder != null) {
// try {
// sdkOutlineBytes = sdkFolder
// .getChildAssumingFile('vm_platform_strong.dill')
// .readAsBytesSync();
// } catch (_) {}
// }
var uriTranslator = new UriTranslatorImpl(
new TargetLibrariesSpecification('none', dartLibraries), packages);
var errorListener = new _ErrorListener();
var options = new CompilerOptions()
..target = new _AnalyzerTarget(
new TargetFlags(strongMode: analysisOptions.strongMode))
..reportMessages = false
..logger = logger
..fileSystem = new _FileSystemAdaptor(fsState, pathContext)
..byteStore = byteStore
..onError = errorListener.onError;
var processedOptions = new ProcessedOptions(options);
return new FrontEndCompiler._(
processedOptions, uriTranslator, errorListener);
}
FrontEndCompiler._(this._options, this.uriTranslator, this._errorListener)
: _logger = _options.logger,
_fileSystem = _options.fileSystem;
/// Compile the library with the given absolute [uri], and everything it
/// depends on. Return the result of the requested library compilation.
///
/// If there is the cached result for the library (compiled directly, or as
/// a result of compilation of another library), it will be returned quickly.
///
/// Throw [StateError] if another compilation is pending.
Future<LibraryCompilationResult> compile(Uri uri) {
if (_isCompileExecuting) {
throw new StateError(MSG_PENDING_COMPILE);
}
_isCompileExecuting = true;
{
LibraryCompilationResult result = _results[uri];
if (result != null) {
_isCompileExecuting = false;
return new Future.value(result);
}
}
return _runWithFrontEndContext('Compile', () async {
try {
var dillTarget =
new DillTarget(_options.ticker, uriTranslator, _options.target);
// Append all libraries what we still have in the current component.
await _logger.runAsync('Load dill libraries', () async {
dillTarget.loader.appendLibraries(_component);
await dillTarget.buildOutlines();
});
// Create the target to compile the library.
var kernelTarget = new _AnalyzerKernelTarget(_fileSystem, dillTarget,
uriTranslator, new AnalyzerMetadataCollector());
kernelTarget.read(uri);
// Compile the entry point into the new component.
_component = await _logger.runAsync('Compile', () async {
await kernelTarget.buildOutlines(nameRoot: _component.root);
return await kernelTarget.buildComponent() ?? _component;
});
// TODO(scheglov) Only for new libraries?
_component.computeCanonicalNames();
_logger.run('Compute dependencies', _computeDependencies);
// TODO(scheglov) Can we keep the same instance?
var types = new TypeEnvironment(
new CoreTypes(_component), new ClassHierarchy(_component));
// Add results for new libraries.
for (var library in _component.libraries) {
if (!_results.containsKey(library.importUri)) {
Map<Uri, List<CollectedResolution>> libraryResolutions =
kernelTarget.resolutions[library.fileUri];
var files = <Uri, FileCompilationResult>{};
void addFileResult(Uri fileUri) {
if (libraryResolutions != null) {
files[fileUri] = new FileCompilationResult(
fileUri,
libraryResolutions[fileUri] ?? [],
_errorListener.fileUriToErrors[fileUri] ?? []);
}
}
addFileResult(library.fileUri);
for (var part in library.parts) {
addFileResult(part.fileUri);
}
var libraryResult = new LibraryCompilationResult(
_component, types, library.importUri, library, files);
_results[library.importUri] = libraryResult;
}
}
_errorListener.fileUriToErrors.clear();
// The result must have been computed.
return _results[uri];
} finally {
_isCompileExecuting = false;
}
});
}
/// Invalidate the file with the given file [uri], its library and the
/// transitive the of libraries that use it. The next time when any of these
/// libraries is be requested in [compile], it will be recompiled again.
void invalidate(Uri uri) {
void invalidateLibrary(Uri libraryUri) {
Library library = _uriToLibrary.remove(libraryUri);
if (library == null) return;
// Invalidate the library.
_component.libraries.remove(library);
_component.root.removeChild('${library.importUri}');
_component.uriToSource.remove(libraryUri);
_results.remove(library.importUri);
// Recursively invalidate dependencies.
Set<Uri> directDependencies =
_directLibraryDependencies.remove(libraryUri);
directDependencies?.forEach(invalidateLibrary);
}
Uri libraryUri = _partToLibrary.remove(uri) ?? uri;
invalidateLibrary(libraryUri);
}
/// Recompute [_directLibraryDependencies] for the current [_component].
void _computeDependencies() {
_directLibraryDependencies.clear();
_uriToLibrary.clear();
_partToLibrary.clear();
void processLibrary(Library library) {
if (_uriToLibrary.containsKey(library.fileUri)) {
return;
}
_uriToLibrary[library.fileUri] = library;
// Remember libraries for parts.
for (var part in library.parts) {
_partToLibrary[part.fileUri] = library.fileUri;
}
// Record reverse dependencies.
for (LibraryDependency dependency in library.dependencies) {
Library targetLibrary = dependency.targetLibrary;
_directLibraryDependencies
.putIfAbsent(targetLibrary.fileUri, () => new Set<Uri>())
.add(library.fileUri);
processLibrary(targetLibrary);
}
}
// Record dependencies for every library in the component.
_component.libraries.forEach(processLibrary);
}
Future<T> _runWithFrontEndContext<T>(String msg, Future<T> f()) async {
return await CompilerContext.runWithOptions(_options, (context) {
context.disableColors();
return _logger.runAsync(msg, f);
});
}
}
/// The compilation result for a single library.
class LibraryCompilationResult {
/// The full current [Component]. It has all libraries that are required by
/// this library, but might also have other libraries, that are not required.
///
/// The object is mutable, and is changed when files are invalidated.
final Component component;
/// The [TypeEnvironment] for the [component].
final TypeEnvironment types;
/// The absolute URI of the library.
final Uri uri;
/// The kernel [Library] of the library.
final Library kernel;
/// The map from file system URIs to results for the defining unit and parts.
final Map<Uri, FileCompilationResult> files;
LibraryCompilationResult(
this.component, this.types, this.uri, this.kernel, this.files);
}
/// The [DietListener] that record resolution information.
class _AnalyzerDietListener extends DietListener {
final Map<Uri, List<CollectedResolution>> _resolutions;
_AnalyzerDietListener(
SourceLibraryBuilder library,
ClassHierarchy hierarchy,
CoreTypes coreTypes,
TypeInferenceEngine typeInferenceEngine,
this._resolutions)
: super(library, hierarchy, coreTypes, typeInferenceEngine);
StackListener createListener(
ModifierBuilder builder, Scope memberScope, bool isInstanceMember,
[Scope formalParameterScope]) {
var fileResolutions = _resolutions[builder.fileUri];
if (fileResolutions == null) {
fileResolutions = <CollectedResolution>[];
_resolutions[builder.fileUri] = fileResolutions;
}
var resolution = new CollectedResolution();
fileResolutions.add(resolution);
return super.createListener(
builder, memberScope, isInstanceMember, formalParameterScope);
}
}
/// The [KernelTarget] that records resolution information.
class _AnalyzerKernelTarget extends KernelTarget {
final Map<Uri, Map<Uri, List<CollectedResolution>>> resolutions = {};
_AnalyzerKernelTarget(front_end.FileSystem fileSystem, DillTarget dillTarget,
UriTranslator uriTranslator, MetadataCollector metadataCollector)
: super(fileSystem, true, dillTarget, uriTranslator,
metadataCollector: metadataCollector);
@override
_AnalyzerSourceLoader<Library> createLoader() {
return new _AnalyzerSourceLoader<Library>(fileSystem, this, resolutions);
}
}
/// The [SourceLoader] that record resolution information.
class _AnalyzerSourceLoader<L> extends SourceLoader<L> {
final Map<Uri, Map<Uri, List<CollectedResolution>>> _resolutions;
_AnalyzerSourceLoader(front_end.FileSystem fileSystem,
TargetImplementation target, this._resolutions)
: super(fileSystem, true, target);
@override
_AnalyzerDietListener createDietListener(LibraryBuilder library) {
var libraryResolutions = <Uri, List<CollectedResolution>>{};
_resolutions[library.fileUri] = libraryResolutions;
return new _AnalyzerDietListener(
library, hierarchy, coreTypes, typeInferenceEngine, libraryResolutions);
}
}
/**
* [Target] for static analysis, with all features enabled.
*/
class _AnalyzerTarget extends NoneTarget {
_AnalyzerTarget(TargetFlags flags) : super(flags);
@override
List<String> get extraRequiredLibraries => const <String>['dart:_internal'];
@override
bool enableNative(Uri uri) => true;
}
/// The listener for [CompilationMessage]s from FrontEnd.
class _ErrorListener {
final Map<Uri, List<CompilationMessage>> fileUriToErrors = {};
void onError(CompilationMessage error) {
var fileUri = error.span.sourceUrl;
fileUriToErrors.putIfAbsent(fileUri, () => []).add(error);
}
}
/// Adapter of [FileSystemState] to [front_end.FileSystem].
class _FileSystemAdaptor implements front_end.FileSystem {
final FileSystemState fsState;
final pathos.Context pathContext;
_FileSystemAdaptor(this.fsState, this.pathContext);
@override
front_end.FileSystemEntity entityForUri(Uri uri) {
if (uri.isScheme('file')) {
var path = pathContext.fromUri(uri);
var file = fsState.getFileForPath(path);
return new _FileSystemEntityAdaptor(uri, file);
} else {
throw new front_end.FileSystemException(
uri, 'Only file:// URIs are supported, but $uri is given.');
}
}
}
/// Adapter of [FileState] to [front_end.FileSystemEntity].
class _FileSystemEntityAdaptor implements front_end.FileSystemEntity {
final Uri uri;
final FileState file;
_FileSystemEntityAdaptor(this.uri, this.file);
@override
Future<bool> exists() async {
return file.exists;
}
@override
Future<List<int>> readAsBytes() async {
// TODO(scheglov) Optimize.
return utf8.encode(file.content);
}
@override
Future<String> readAsString() async {
return file.content;
}
}