| // Copyright (c) 2016, 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. |
| |
| library fasta.source_loader; |
| |
| import 'dart:async' show Future; |
| |
| import 'dart:typed_data' show Uint8List; |
| |
| import 'package:front_end/file_system.dart'; |
| import 'package:front_end/src/base/instrumentation.dart' show Instrumentation; |
| |
| import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart' |
| show KernelTypeInferenceEngine; |
| |
| import 'package:front_end/src/fasta/kernel/kernel_target.dart' |
| show KernelTarget; |
| |
| import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart' |
| show TypeInferenceEngine; |
| |
| import 'package:kernel/ast.dart' show Arguments, Expression, Program; |
| |
| import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; |
| |
| import 'package:kernel/core_types.dart' show CoreTypes; |
| |
| import 'package:kernel/src/incremental_class_hierarchy.dart' |
| show IncrementalClassHierarchy; |
| |
| import '../builder/builder.dart' |
| show |
| Builder, |
| ClassBuilder, |
| EnumBuilder, |
| LibraryBuilder, |
| NamedTypeBuilder, |
| TypeBuilder; |
| |
| import '../compiler_context.dart' show CompilerContext; |
| |
| import '../errors.dart' show inputError; |
| |
| import '../export.dart' show Export; |
| |
| import '../loader.dart' show Loader; |
| |
| import '../parser/class_member_parser.dart' show ClassMemberParser; |
| |
| import '../scanner.dart' show ErrorToken, ScannerResult, Token, scan; |
| |
| import 'diet_listener.dart' show DietListener; |
| |
| import 'diet_parser.dart' show DietParser; |
| |
| import 'outline_builder.dart' show OutlineBuilder; |
| |
| import 'source_class_builder.dart' show SourceClassBuilder; |
| |
| import 'source_library_builder.dart' show SourceLibraryBuilder; |
| |
| class SourceLoader<L> extends Loader<L> { |
| /// The [FileSystem] which should be used to access files. |
| final FileSystem fileSystem; |
| |
| final Map<Uri, List<int>> sourceBytes = <Uri, List<int>>{}; |
| final bool excludeSource = CompilerContext.current.options.excludeSource; |
| |
| // Used when building directly to kernel. |
| ClassHierarchy hierarchy; |
| CoreTypes coreTypes; |
| |
| TypeInferenceEngine typeInferenceEngine; |
| |
| Instrumentation instrumentation; |
| |
| SourceLoader(this.fileSystem, KernelTarget target) : super(target); |
| |
| Future<Token> tokenize(SourceLibraryBuilder library, |
| {bool suppressLexicalErrors: false}) async { |
| Uri uri = library.fileUri; |
| if (uri == null || uri.scheme != "file") { |
| return inputError(library.uri, -1, "Not found: ${library.uri}."); |
| } |
| |
| // Get the library text from the cache, or read from the file system. |
| List<int> bytes = sourceBytes[uri]; |
| if (bytes == null) { |
| try { |
| List<int> rawBytes = await fileSystem.entityForUri(uri).readAsBytes(); |
| Uint8List zeroTerminatedBytes = new Uint8List(rawBytes.length + 1); |
| zeroTerminatedBytes.setRange(0, rawBytes.length, rawBytes); |
| bytes = zeroTerminatedBytes; |
| sourceBytes[uri] = bytes; |
| } on FileSystemException catch (e) { |
| return inputError(uri, -1, e.message); |
| } |
| } |
| |
| byteCount += bytes.length - 1; |
| ScannerResult result = scan(bytes); |
| Token token = result.tokens; |
| if (!suppressLexicalErrors) { |
| List<int> source = getSource(bytes); |
| target.addSourceInformation(library.fileUri, result.lineStarts, source); |
| } |
| while (token is ErrorToken) { |
| if (!suppressLexicalErrors) { |
| ErrorToken error = token; |
| library.addCompileTimeError(token.charOffset, error.assertionMessage, |
| fileUri: uri); |
| } |
| token = token.next; |
| } |
| return token; |
| } |
| |
| List<int> getSource(List<int> bytes) { |
| if (excludeSource) return const <int>[]; |
| |
| // bytes is 0-terminated. We don't want that included. |
| if (bytes is Uint8List) { |
| return new Uint8List.view( |
| bytes.buffer, bytes.offsetInBytes, bytes.length - 1); |
| } |
| return bytes.sublist(0, bytes.length - 1); |
| } |
| |
| Future<Null> buildOutline(SourceLibraryBuilder library) async { |
| Token tokens = await tokenize(library); |
| if (tokens == null) return; |
| OutlineBuilder listener = new OutlineBuilder(library); |
| new ClassMemberParser(listener).parseUnit(tokens); |
| } |
| |
| Future<Null> buildBody(LibraryBuilder library) async { |
| if (library is SourceLibraryBuilder) { |
| // We tokenize source files twice to keep memory usage low. This is the |
| // second time, and the first time was in [buildOutline] above. So this |
| // time we suppress lexical errors. |
| Token tokens = await tokenize(library, suppressLexicalErrors: true); |
| if (tokens == null) return; |
| DietListener listener = createDietListener(library); |
| DietParser parser = new DietParser(listener); |
| parser.parseUnit(tokens); |
| for (SourceLibraryBuilder part in library.parts) { |
| Token tokens = await tokenize(part); |
| if (tokens != null) { |
| listener.uri = part.fileUri; |
| parser.parseUnit(tokens); |
| } |
| } |
| } |
| } |
| |
| KernelTarget get target => super.target; |
| |
| DietListener createDietListener(LibraryBuilder library) { |
| return new DietListener(library, hierarchy, coreTypes, typeInferenceEngine); |
| } |
| |
| void resolveParts() { |
| List<Uri> parts = <Uri>[]; |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| if (library is SourceLibraryBuilder) { |
| if (library.isPart) { |
| library.validatePart(); |
| parts.add(uri); |
| } else { |
| library.includeParts(); |
| } |
| } |
| }); |
| parts.forEach(builders.remove); |
| ticker.logMs("Resolved parts"); |
| } |
| |
| void computeLibraryScopes() { |
| Set<LibraryBuilder> exporters = new Set<LibraryBuilder>(); |
| Set<LibraryBuilder> exportees = new Set<LibraryBuilder>(); |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| if (library is SourceLibraryBuilder) { |
| library.buildInitialScopes(); |
| } |
| if (library.exporters.isNotEmpty) { |
| exportees.add(library); |
| for (Export exporter in library.exporters) { |
| exporters.add(exporter.exporter); |
| } |
| } |
| }); |
| Set<SourceLibraryBuilder> both = new Set<SourceLibraryBuilder>(); |
| for (LibraryBuilder exported in exportees) { |
| if (exporters.contains(exported)) { |
| both.add(exported); |
| } |
| for (Export export in exported.exporters) { |
| exported.exports.forEach(export.addToExportScope); |
| } |
| } |
| bool wasChanged = false; |
| do { |
| wasChanged = false; |
| for (SourceLibraryBuilder exported in both) { |
| for (Export export in exported.exporters) { |
| SourceLibraryBuilder exporter = export.exporter; |
| exported.exports.forEach((String name, Builder member) { |
| if (exporter.addToExportScope(name, member)) { |
| wasChanged = true; |
| } |
| }); |
| } |
| } |
| } while (wasChanged); |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| if (library is SourceLibraryBuilder) { |
| library.addImportsToScope(); |
| } |
| }); |
| ticker.logMs("Computed library scopes"); |
| // debugPrintExports(); |
| } |
| |
| void debugPrintExports() { |
| // TODO(sigmund): should be `covarint SourceLibraryBuilder`. |
| builders.forEach((Uri uri, dynamic l) { |
| SourceLibraryBuilder library = l; |
| Set<Builder> members = new Set<Builder>(); |
| library.forEach((String name, Builder member) { |
| while (member != null) { |
| members.add(member); |
| member = member.next; |
| } |
| }); |
| List<String> exports = <String>[]; |
| library.exports.forEach((String name, Builder member) { |
| while (member != null) { |
| if (!members.contains(member)) { |
| exports.add(name); |
| } |
| member = member.next; |
| } |
| }); |
| if (exports.isNotEmpty) { |
| print("$uri exports $exports"); |
| } |
| }); |
| } |
| |
| void resolveTypes() { |
| int typeCount = 0; |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| typeCount += library.resolveTypes(null); |
| }); |
| ticker.logMs("Resolved $typeCount types"); |
| } |
| |
| void finishStaticInvocations() { |
| int count = 0; |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| count += library.finishStaticInvocations(); |
| }); |
| ticker.logMs("Finished static invocations $count"); |
| } |
| |
| void resolveConstructors() { |
| int count = 0; |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| count += library.resolveConstructors(null); |
| }); |
| ticker.logMs("Resolved $count constructors"); |
| } |
| |
| void finishTypeVariables(ClassBuilder object) { |
| int count = 0; |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| count += library.finishTypeVariables(object); |
| }); |
| ticker.logMs("Resolved $count type-variable bounds"); |
| } |
| |
| void finishNativeMethods() { |
| int count = 0; |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| count += library.finishNativeMethods(); |
| }); |
| ticker.logMs("Finished $count native methods"); |
| } |
| |
| /// Returns all the supertypes (including interfaces) of [cls] |
| /// transitively. Includes [cls]. |
| Set<ClassBuilder> allSupertypes(ClassBuilder cls) { |
| int length = 0; |
| Set<ClassBuilder> result = new Set<ClassBuilder>()..add(cls); |
| while (length != result.length) { |
| length = result.length; |
| result.addAll(directSupertypes(result)); |
| } |
| return result; |
| } |
| |
| /// Returns the direct supertypes (including interface) of [classes]. A class |
| /// from [classes] is only included if it is a supertype of one of the other |
| /// classes in [classes]. |
| Set<ClassBuilder> directSupertypes(Iterable<ClassBuilder> classes) { |
| Set<ClassBuilder> result = new Set<ClassBuilder>(); |
| for (ClassBuilder cls in classes) { |
| target.addDirectSupertype(cls, result); |
| } |
| return result; |
| } |
| |
| /// Computes a set of classes that may have cycles. The set is empty if there |
| /// are no cycles. If the set isn't empty, it will include supertypes of |
| /// classes with cycles, as well as the classes with cycles. |
| /// |
| /// It is assumed that [classes] is a transitive closure with respect to |
| /// supertypes. |
| Iterable<ClassBuilder> cyclicCandidates(Iterable<ClassBuilder> classes) { |
| // The candidates are found by a fixed-point computation. |
| // |
| // On each iteration, the classes that have no supertypes in the input set |
| // will be removed. |
| // |
| // If there are no cycles, eventually, the set will converge on Object, and |
| // the next iteration will make the set empty (as Object has no |
| // supertypes). |
| // |
| // On the other hand, if there is a cycle, the cycle will remain in the |
| // set, and so will its supertypes, and eventually the input and output set |
| // will have the same length. |
| Iterable<ClassBuilder> input = const []; |
| Iterable<ClassBuilder> output = classes; |
| while (input.length != output.length) { |
| input = output; |
| output = directSupertypes(input); |
| } |
| return output; |
| } |
| |
| /// Whether [library] is allowed to define classes that extend or implement |
| /// restricted types, such as `bool`, `int`, `double`, `num`, and `String`. By |
| /// default this is only allowed within the implementation of `dart:core`, but |
| /// some target implementations may need to override this to allow doing this |
| /// in other internal platform libraries. |
| bool canImplementRestrictedTypes(LibraryBuilder library) => |
| library == coreLibrary; |
| |
| void checkSemantics() { |
| List<ClassBuilder> allClasses = target.collectAllClasses(); |
| Iterable<ClassBuilder> candidates = cyclicCandidates(allClasses); |
| Map<ClassBuilder, Set<ClassBuilder>> realCycles = |
| <ClassBuilder, Set<ClassBuilder>>{}; |
| for (ClassBuilder cls in candidates) { |
| Set<ClassBuilder> cycles = cyclicCandidates(allSupertypes(cls)); |
| if (cycles.isNotEmpty) { |
| realCycles[cls] = cycles; |
| } |
| } |
| Set<ClassBuilder> reported = new Set<ClassBuilder>(); |
| realCycles.forEach((ClassBuilder cls, Set<ClassBuilder> cycles) { |
| target.breakCycle(cls); |
| if (reported.add(cls)) { |
| List<ClassBuilder> involved = <ClassBuilder>[]; |
| for (ClassBuilder cls in cycles) { |
| if (realCycles.containsKey(cls)) { |
| involved.add(cls); |
| reported.add(cls); |
| } |
| } |
| String involvedString = |
| involved.map((c) => c.fullNameForErrors).join("', '"); |
| cls.addCompileTimeError( |
| cls.charOffset, |
| "'${cls.fullNameForErrors}' is a supertype of itself via " |
| "'$involvedString'."); |
| } |
| }); |
| ticker.logMs("Found cycles"); |
| Set<ClassBuilder> blackListedClasses = new Set<ClassBuilder>.from([ |
| coreLibrary["bool"], |
| coreLibrary["int"], |
| coreLibrary["num"], |
| coreLibrary["double"], |
| coreLibrary["String"], |
| ]); |
| for (ClassBuilder cls in allClasses) { |
| if (cls.library.loader != this) continue; |
| Set<ClassBuilder> directSupertypes = new Set<ClassBuilder>(); |
| target.addDirectSupertype(cls, directSupertypes); |
| for (ClassBuilder supertype in directSupertypes) { |
| if (supertype is EnumBuilder) { |
| cls.addCompileTimeError( |
| cls.charOffset, |
| "'${supertype.name}' is an enum and can't be extended or " |
| "implemented."); |
| } else if (!canImplementRestrictedTypes(cls.library) && |
| blackListedClasses.contains(supertype)) { |
| cls.addCompileTimeError( |
| cls.charOffset, |
| "'${supertype.name}' is restricted and can't be extended or " |
| "implemented."); |
| } |
| } |
| TypeBuilder mixedInType = cls.mixedInType; |
| if (mixedInType != null) { |
| bool isClassBuilder = false; |
| if (mixedInType is NamedTypeBuilder) { |
| var builder = mixedInType.builder; |
| if (builder is ClassBuilder) { |
| isClassBuilder = true; |
| for (Builder constructory in builder.constructors.local.values) { |
| if (constructory.isConstructor && !constructory.isSynthetic) { |
| cls.addCompileTimeError( |
| cls.charOffset, |
| "Can't use '${builder.fullNameForErrors}' as a mixin " |
| "because it has constructors."); |
| builder.addCompileTimeError( |
| constructory.charOffset, |
| "This constructor prevents using " |
| "'${builder.fullNameForErrors}' as a mixin."); |
| } |
| } |
| } |
| } |
| if (!isClassBuilder) { |
| cls.addCompileTimeError(cls.charOffset, |
| "The type '${mixedInType.fullNameForErrors}' can't be mixed in."); |
| } |
| } |
| } |
| ticker.logMs("Checked restricted supertypes"); |
| } |
| |
| void buildProgram() { |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| if (library is SourceLibraryBuilder) { |
| libraries.add(library.build(coreLibrary)); |
| } |
| }); |
| ticker.logMs("Built program"); |
| } |
| |
| void computeHierarchy(Program program) { |
| hierarchy = new IncrementalClassHierarchy(); |
| ticker.logMs("Computed class hierarchy"); |
| coreTypes = new CoreTypes(program); |
| ticker.logMs("Computed core types"); |
| } |
| |
| void checkOverrides(List<SourceClassBuilder> sourceClasses) { |
| assert(hierarchy != null); |
| for (SourceClassBuilder builder in sourceClasses) { |
| builder.checkOverrides(hierarchy); |
| } |
| ticker.logMs("Checked overrides"); |
| } |
| |
| void createTypeInferenceEngine() { |
| typeInferenceEngine = |
| new KernelTypeInferenceEngine(instrumentation, target.strongMode); |
| } |
| |
| /// Performs the first phase of top level initializer inference, which |
| /// consists of creating kernel objects for all fields and top level variables |
| /// that might be subject to type inference, and records dependencies between |
| /// them. |
| void prepareInitializerInference() { |
| typeInferenceEngine.prepareTopLevel(coreTypes, hierarchy); |
| builders.forEach((Uri uri, LibraryBuilder library) { |
| if (library is SourceLibraryBuilder) { |
| library.prepareInitializerInference(library, null); |
| } |
| }); |
| ticker.logMs("Prepared initializer inference"); |
| } |
| |
| /// Performs the second phase of top level initializer inference, which is to |
| /// visit fields and top level variables in topologically-sorted order and |
| /// assign their types. |
| void performInitializerInference() { |
| typeInferenceEngine.finishTopLevel(); |
| ticker.logMs("Performed initializer inference"); |
| } |
| |
| List<Uri> getDependencies() => sourceBytes.keys.toList(); |
| |
| Expression instantiateInvocation(Expression receiver, String name, |
| Arguments arguments, int offset, bool isSuper) { |
| return target.backendTarget.instantiateInvocation( |
| coreTypes, receiver, name, arguments, offset, isSuper); |
| } |
| |
| Expression instantiateNoSuchMethodError( |
| Expression receiver, String name, Arguments arguments, int offset, |
| {bool isMethod: false, |
| bool isGetter: false, |
| bool isSetter: false, |
| bool isField: false, |
| bool isLocalVariable: false, |
| bool isDynamic: false, |
| bool isSuper: false, |
| bool isStatic: false, |
| bool isConstructor: false, |
| bool isTopLevel: false}) { |
| return target.backendTarget.instantiateNoSuchMethodError( |
| coreTypes, receiver, name, arguments, offset, |
| isMethod: isMethod, |
| isGetter: isGetter, |
| isSetter: isSetter, |
| isField: isField, |
| isLocalVariable: isLocalVariable, |
| isDynamic: isDynamic, |
| isSuper: isSuper, |
| isStatic: isStatic, |
| isConstructor: isConstructor, |
| isTopLevel: isTopLevel); |
| } |
| |
| Expression throwCompileConstantError(Expression error) { |
| return target.backendTarget.throwCompileConstantError(coreTypes, error); |
| } |
| |
| Expression buildCompileTimeError(String message, int offset) { |
| return target.backendTarget |
| .buildCompileTimeError(coreTypes, message, offset); |
| } |
| } |