| // 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:collection' show Queue; |
| import 'dart:convert' show utf8; |
| import 'dart:typed_data' show Uint8List; |
| |
| import 'package:_fe_analyzer_shared/src/parser/class_member_parser.dart' |
| show ClassMemberParser; |
| import 'package:_fe_analyzer_shared/src/parser/forwarding_listener.dart' |
| show ForwardingListener; |
| import 'package:_fe_analyzer_shared/src/parser/parser.dart' |
| show Parser, lengthForToken; |
| import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' |
| show |
| ErrorToken, |
| LanguageVersionToken, |
| Scanner, |
| ScannerConfiguration, |
| ScannerResult, |
| Token, |
| scan; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; |
| import 'package:kernel/core_types.dart' show CoreTypes; |
| import 'package:kernel/reference_from_index.dart' |
| show IndexedLibrary, ReferenceFromIndex; |
| import 'package:kernel/target/targets.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:kernel/util/graph.dart'; |
| import 'package:package_config/package_config.dart' as package_config; |
| |
| import '../api_prototype/experimental_flags.dart'; |
| import '../api_prototype/file_system.dart'; |
| import '../base/builder_graph.dart'; |
| import '../base/common.dart'; |
| import '../base/export.dart' show Export; |
| import '../base/import_chains.dart'; |
| import '../base/instrumentation.dart' show Instrumentation; |
| import '../base/loader.dart' show Loader, untranslatableUriScheme; |
| import '../base/local_scope.dart'; |
| import '../base/nnbd_mode.dart'; |
| import '../base/problems.dart' show internalProblem; |
| import '../base/scope.dart'; |
| import '../base/ticker.dart' show Ticker; |
| import '../base/uri_offset.dart'; |
| import '../base/uris.dart'; |
| import '../builder/builder.dart'; |
| import '../builder/declaration_builders.dart'; |
| import '../builder/library_builder.dart'; |
| import '../builder/member_builder.dart'; |
| import '../builder/name_iterator.dart'; |
| import '../builder/named_type_builder.dart'; |
| import '../builder/nullability_builder.dart'; |
| import '../builder/omitted_type_builder.dart'; |
| import '../builder/type_builder.dart'; |
| import '../codes/cfe_codes.dart'; |
| import '../codes/denylisted_classes.dart' |
| show denylistedCoreClasses, denylistedTypedDataClasses; |
| import '../dill/dill_library_builder.dart'; |
| import '../kernel/benchmarker.dart' show BenchmarkSubdivides; |
| import '../kernel/body_builder.dart' show BodyBuilder; |
| import '../kernel/body_builder_context.dart'; |
| import '../kernel/exhaustiveness.dart'; |
| import '../kernel/hierarchy/class_member.dart'; |
| import '../kernel/hierarchy/delayed.dart'; |
| import '../kernel/hierarchy/hierarchy_builder.dart'; |
| import '../kernel/hierarchy/hierarchy_node.dart'; |
| import '../kernel/hierarchy/members_builder.dart'; |
| import '../kernel/kernel_helper.dart' |
| show DelayedDefaultValueCloner, TypeDependency; |
| import '../kernel/kernel_target.dart' show KernelTarget; |
| import '../kernel/macro/macro.dart'; |
| import '../kernel/type_builder_computer.dart' show TypeBuilderComputer; |
| import '../macros/macro_injected_impl.dart' as injected; |
| import '../type_inference/type_inference_engine.dart'; |
| import '../type_inference/type_inferrer.dart'; |
| import 'diet_listener.dart' show DietListener; |
| import 'diet_parser.dart' show DietParser, useImplicitCreationExpressionInCfe; |
| import 'name_scheme.dart'; |
| import 'offset_map.dart'; |
| import 'outline_builder.dart' show OutlineBuilder; |
| import 'source_class_builder.dart' show SourceClassBuilder; |
| import 'source_constructor_builder.dart'; |
| import 'source_enum_builder.dart'; |
| import 'source_extension_type_declaration_builder.dart'; |
| import 'source_factory_builder.dart'; |
| import 'source_library_builder.dart' |
| show |
| ImplicitLanguageVersion, |
| InvalidLanguageVersion, |
| LanguageVersion, |
| LibraryAccess, |
| SourceLibraryBuilder; |
| import 'source_procedure_builder.dart'; |
| import 'stack_listener_impl.dart' show offsetForToken; |
| |
| class SourceLoader extends Loader { |
| /// The [FileSystem] which should be used to access files. |
| final FileSystem fileSystem; |
| |
| /// Whether comments should be scanned and parsed. |
| final bool includeComments; |
| |
| final Map<Uri, Uint8List> sourceBytes = <Uri, Uint8List>{}; |
| |
| ClassHierarchyBuilder? _hierarchyBuilder; |
| |
| ClassMembersBuilder? _membersBuilder; |
| |
| ReferenceFromIndex? referenceFromIndex; |
| |
| /// Used when building directly to kernel. |
| ClassHierarchy? _hierarchy; |
| CoreTypes? _coreTypes; |
| TypeEnvironment? _typeEnvironment; |
| |
| /// For builders created with a reference, this maps from that reference to |
| /// that builder. This is used for looking up source builders when finalizing |
| /// exports in dill builders. |
| Map<Reference, Builder> buildersCreatedWithReferences = {}; |
| |
| /// Used when checking whether a return type of an async function is valid. |
| /// |
| /// The said return type is valid if it's a subtype of [futureOfBottom]. |
| DartType? _futureOfBottom; |
| |
| DartType get futureOfBottom => _futureOfBottom!; |
| |
| /// Used when checking whether a return type of a sync* function is valid. |
| /// |
| /// The said return type is valid if it's a subtype of [iterableOfBottom]. |
| DartType? _iterableOfBottom; |
| |
| DartType get iterableOfBottom => _iterableOfBottom!; |
| |
| /// Used when checking whether a return type of an async* function is valid. |
| /// |
| /// The said return type is valid if it's a subtype of [streamOfBottom]. |
| DartType? _streamOfBottom; |
| |
| DartType get streamOfBottom => _streamOfBottom!; |
| |
| TypeInferenceEngineImpl? _typeInferenceEngine; |
| |
| Instrumentation? instrumentation; |
| |
| final SourceLoaderDataForTesting? dataForTesting; |
| |
| final Map<Uri, CompilationUnit> _compilationUnits = {}; |
| |
| Map<Uri, LibraryBuilder> _loadedLibraryBuilders = <Uri, LibraryBuilder>{}; |
| |
| List<SourceLibraryBuilder>? _sourceLibraryBuilders; |
| |
| final Queue<SourceCompilationUnit> _unparsedLibraries = |
| new Queue<SourceCompilationUnit>(); |
| |
| final List<Library> libraries = <Library>[]; |
| |
| final KernelTarget target; |
| |
| /// List of all handled compile-time errors seen so far by libraries loaded |
| /// by this loader. |
| /// |
| /// A handled error is an error that has been added to the generated AST |
| /// already, for example, as a throw expression. |
| final List<LocatedMessage> handledErrors = <LocatedMessage>[]; |
| |
| /// List of all unhandled compile-time errors seen so far by libraries loaded |
| /// by this loader. |
| /// |
| /// An unhandled error is an error that hasn't been handled, see |
| /// [handledErrors]. |
| final List<LocatedMessage> unhandledErrors = <LocatedMessage>[]; |
| |
| /// List of all problems seen so far by libraries loaded by this loader that |
| /// does not belong directly to a library. |
| final List<FormattedMessage> allComponentProblems = <FormattedMessage>[]; |
| |
| /// The text of the messages that have been reported. |
| /// |
| /// This is used filter messages so that we don't report the same error twice. |
| final Set<String> seenMessages = new Set<String>(); |
| |
| /// Set to `true` if one of the reported errors had severity `Severity.error`. |
| /// |
| /// This is used for [hasSeenError]. |
| bool _hasSeenError = false; |
| |
| // Coverage-ignore(suite): Not run. |
| /// Clears the [seenMessages] and [hasSeenError] state. |
| void resetSeenMessages() { |
| seenMessages.clear(); |
| _hasSeenError = false; |
| } |
| |
| /// Returns `true` if a compile time error has been reported. |
| bool get hasSeenError => _hasSeenError; |
| |
| LibraryBuilder? _coreLibrary; |
| CompilationUnit? _coreLibraryCompilationUnit; |
| LibraryBuilder? typedDataLibrary; |
| |
| final Set<Uri> roots = {}; |
| |
| // TODO(johnniwinther): Replace with a `singleRoot`. |
| // See also https://dart-review.googlesource.com/c/sdk/+/273381. |
| LibraryBuilder? get rootLibrary { |
| for (Uri uri in roots) { |
| LibraryBuilder? builder = lookupLoadedLibraryBuilder(uri); |
| if (builder != null) return builder; |
| } |
| return null; |
| } |
| |
| CompilationUnit? get rootCompilationUnit { |
| for (Uri uri in roots) { |
| CompilationUnit? builder = _compilationUnits[uri]; |
| if (builder != null) return builder; |
| } |
| return null; |
| } |
| |
| int byteCount = 0; |
| |
| UriOffset? currentUriForCrashReporting; |
| |
| ClassBuilder? _macroClassBuilder; |
| |
| /// The macro declarations that are currently being compiled. |
| Set<ClassBuilder> _macroDeclarations = {}; |
| |
| final List<String> _expectedOutlineFutureProblems = []; |
| final List<String> _expectedBodyBuildingFutureProblems = []; |
| |
| SourceLoader(this.fileSystem, this.includeComments, this.target) |
| : dataForTesting = retainDataForTesting |
| ? |
| // Coverage-ignore(suite): Not run. |
| new SourceLoaderDataForTesting() |
| : null; |
| |
| void installAllProblemsIntoComponent(Component component, |
| {required CompilationPhaseForProblemReporting currentPhase}) { |
| List<String> expectedFutureProblemsForCurrentPhase = switch (currentPhase) { |
| CompilationPhaseForProblemReporting.outline => |
| _expectedOutlineFutureProblems, |
| CompilationPhaseForProblemReporting.bodyBuilding => |
| _expectedBodyBuildingFutureProblems |
| }; |
| assert( |
| expectedFutureProblemsForCurrentPhase.isEmpty || hasSeenError, |
| // Coverage-ignore(suite): Not run. |
| "Expected problems to be reported, but there were none.\n" |
| "Current compilation phase: ${currentPhase}\n" |
| "Expected at these locations:\n" |
| " * ${expectedFutureProblemsForCurrentPhase.join("\n * ")}"); |
| if (allComponentProblems.isNotEmpty) { |
| component.problemsAsJson ??= <String>[]; |
| } |
| for (int i = 0; i < allComponentProblems.length; i++) { |
| FormattedMessage formattedMessage = allComponentProblems[i]; |
| component.problemsAsJson!.add(formattedMessage.toJsonString()); |
| } |
| allComponentProblems.clear(); |
| } |
| |
| /// Assert that a compile-time error was reported during [expectedPhase] of |
| /// compilation. |
| /// |
| /// The parameters [location] and [originalStackTrace] are supposed to help to |
| /// locate the place where the expectation was declared. |
| /// |
| /// To avoid spending resources on stack trace computations, it is recommended |
| /// to wrap the calls to [assertProblemReportedElsewhere] into `assert`s. |
| bool assertProblemReportedElsewhere(String location, |
| {required CompilationPhaseForProblemReporting expectedPhase}) { |
| if (hasSeenError) return true; |
| List<String> expectedFutureProblemsForCurrentPhase = |
| switch (expectedPhase) { |
| CompilationPhaseForProblemReporting.outline => |
| _expectedOutlineFutureProblems, |
| CompilationPhaseForProblemReporting.bodyBuilding => |
| _expectedBodyBuildingFutureProblems |
| }; |
| expectedFutureProblemsForCurrentPhase |
| .add("${location}\n${StackTrace.current}\n"); |
| return true; |
| } |
| |
| bool containsLoadedLibraryBuilder(Uri importUri) => |
| lookupLoadedLibraryBuilder(importUri) != null; |
| |
| LibraryBuilder? lookupLoadedLibraryBuilder(Uri importUri) { |
| return _loadedLibraryBuilders[importUri]; |
| } |
| |
| CompilationUnit? lookupCompilationUnit(Uri importUri) => |
| _compilationUnits[importUri]; |
| |
| // Coverage-ignore(suite): Not run. |
| CompilationUnit? lookupCompilationUnitByFileUri(Uri fileUri) { |
| // TODO(johnniwinther): Store compilation units in a map by file URI? |
| for (CompilationUnit compilationUnit in _compilationUnits.values) { |
| if (compilationUnit.fileUri == fileUri) { |
| return compilationUnit; |
| } |
| } |
| return null; |
| } |
| |
| Iterable<CompilationUnit> get compilationUnits => _compilationUnits.values; |
| |
| Iterable<LibraryBuilder> get loadedLibraryBuilders { |
| return _loadedLibraryBuilders.values; |
| } |
| |
| /// The [SourceLibraryBuilder]s for the libraries built from source by this |
| /// source loader. |
| /// |
| /// This is available after [resolveParts] have been called and doesn't |
| /// include parts or augmentations. Orphaned parts _are_ included. |
| List<SourceLibraryBuilder> get sourceLibraryBuilders { |
| assert( |
| _sourceLibraryBuilders != null, |
| "Source library builder hasn't been computed yet. " |
| "The source libraries are in SourceLoader.resolveParts."); |
| return _sourceLibraryBuilders!; |
| } |
| |
| void clearSourceLibraryBuilders() { |
| assert( |
| _sourceLibraryBuilders != null, |
| "Source library builder hasn't been computed yet. " |
| "The source libraries are in SourceLoader.resolveParts."); |
| _sourceLibraryBuilders!.clear(); |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| Iterable<Uri> get loadedLibraryImportUris => _loadedLibraryBuilders.keys; |
| |
| void registerLoadedDillLibraryBuilder(DillLibraryBuilder libraryBuilder) { |
| assert( |
| !libraryBuilder.isPart, // Coverage-ignore(suite): Not run. |
| "Unexpected part $libraryBuilder."); |
| assert( |
| !libraryBuilder.isAugmenting, |
| // Coverage-ignore(suite): Not run. |
| "Unexpected augmenting library $libraryBuilder."); |
| Uri uri = libraryBuilder.importUri; |
| _markDartLibraries(uri, libraryBuilder, libraryBuilder.mainCompilationUnit); |
| _compilationUnits[uri] = libraryBuilder.mainCompilationUnit; |
| _loadedLibraryBuilders[uri] = libraryBuilder; |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| LibraryBuilder? deregisterLoadedLibraryBuilder(Uri importUri) { |
| LibraryBuilder? libraryBuilder = _loadedLibraryBuilders.remove(importUri); |
| if (libraryBuilder != null) { |
| _compilationUnits.remove(importUri); |
| } |
| return libraryBuilder; |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| void clearLibraryBuilders() { |
| _compilationUnits.clear(); |
| _loadedLibraryBuilders.clear(); |
| } |
| |
| /// Run [f] with [uri] and [fileOffset] as the current uri/offset used for |
| /// reporting crashes. |
| T withUriForCrashReporting<T>(Uri uri, int fileOffset, T Function() f) { |
| UriOffset? oldUriForCrashReporting = currentUriForCrashReporting; |
| currentUriForCrashReporting = new UriOffset(uri, fileOffset); |
| T result = f(); |
| currentUriForCrashReporting = oldUriForCrashReporting; |
| return result; |
| } |
| |
| @override |
| LibraryBuilder get coreLibrary => _coreLibrary!; |
| |
| @override |
| CompilationUnit get coreLibraryCompilationUnit => |
| _coreLibraryCompilationUnit!; |
| |
| Ticker get ticker => target.ticker; |
| |
| /// Creates a [SourceLibraryBuilder] corresponding to [importUri], if one |
| /// doesn't exist already. |
| /// |
| /// [fileUri] must not be null and is a URI that can be passed to FileSystem |
| /// to locate the corresponding file. |
| /// |
| /// [origin] is non-null if the created library is an augmentation of |
| /// [origin]. |
| /// |
| /// [packageUri] is the base uri for the package which the library belongs to. |
| /// For instance 'package:foo'. |
| /// |
| /// This is used to associate libraries in for instance the 'bin' and 'test' |
| /// folders of a package source with the package uri of the 'lib' folder. |
| /// |
| /// If the [packageUri] is `null` the package association of this library is |
| /// based on its [importUri]. |
| /// |
| /// For libraries with a 'package:' [importUri], the package path must match |
| /// the path in the [importUri]. For libraries with a 'dart:' [importUri] the |
| /// [packageUri] must be `null`. |
| /// |
| /// [packageLanguageVersion] is the language version defined by the package |
| /// which the library belongs to, or the current sdk version if the library |
| /// doesn't belong to a package. |
| SourceLibraryBuilder createLibraryBuilder( |
| {required Uri importUri, |
| required Uri fileUri, |
| Uri? packageUri, |
| required Uri originImportUri, |
| required LanguageVersion packageLanguageVersion, |
| SourceLibraryBuilder? origin, |
| IndexedLibrary? referencesFromIndex, |
| bool? referenceIsPartOwner, |
| bool isAugmentation = false, |
| bool isPatch = false}) { |
| return new SourceLibraryBuilder( |
| importUri: importUri, |
| fileUri: fileUri, |
| packageUri: packageUri, |
| originImportUri: originImportUri, |
| packageLanguageVersion: packageLanguageVersion, |
| loader: this, |
| origin: origin, |
| indexedLibrary: referencesFromIndex, |
| referenceIsPartOwner: referenceIsPartOwner, |
| isUnsupported: origin?.library.isUnsupported ?? |
| importUri.isScheme('dart') && |
| !target.uriTranslator.isLibrarySupported(importUri.path), |
| isAugmentation: isAugmentation, |
| isPatch: isPatch); |
| } |
| |
| /// Return `"true"` if the [dottedName] is a 'dart.library.*' qualifier for a |
| /// supported dart:* library, and `null` otherwise. |
| /// |
| /// This is used to determine conditional imports and `bool.fromEnvironment` |
| /// constant values for "dart.library.[libraryName]" values. |
| /// |
| /// The `null` value will not be equal to the tested string value of |
| /// a configurable URI, which is always non-`null`. This prevents |
| /// the configurable URI from matching an absent entry, |
| /// even for an `if (dart.library.nonLibrary == "")` test. |
| String? getLibrarySupportValue(String dottedName) { |
| if (!DartLibrarySupport.isDartLibraryQualifier(dottedName)) { |
| return ""; |
| } |
| String libraryName = DartLibrarySupport.getDartLibraryName(dottedName); |
| Uri uri = new Uri(scheme: "dart", path: libraryName); |
| // TODO(johnniwinther): This should really be libraries only. |
| CompilationUnit? compilationUnit = lookupCompilationUnit(uri); |
| // TODO(johnniwinther): Why is the dill target sometimes not loaded at this |
| // point? And does it matter? |
| compilationUnit ??= target.dillTarget.loader |
| .lookupLibraryBuilder(uri) |
| // Coverage-ignore(suite): Not run. |
| ?.mainCompilationUnit; |
| return DartLibrarySupport.isDartLibrarySupported(libraryName, |
| libraryExists: compilationUnit != null, |
| isSynthetic: compilationUnit?.isSynthetic ?? true, |
| isUnsupported: compilationUnit?.isUnsupported ?? true, |
| dartLibrarySupport: target.backendTarget.dartLibrarySupport) |
| ? "true" |
| : null; |
| } |
| |
| SourceCompilationUnit _createSourceCompilationUnit( |
| {required Uri uri, |
| required Uri? fileUri, |
| required Uri? originImportUri, |
| required SourceLibraryBuilder? origin, |
| required IndexedLibrary? referencesFromIndex, |
| required bool? referenceIsPartOwner, |
| required bool isAugmentation, |
| required bool isPatch, |
| required bool addAsRoot}) { |
| if (fileUri != null && |
| (fileUri.isScheme("dart") || |
| fileUri.isScheme("package") || |
| fileUri.isScheme("dart-ext"))) { |
| fileUri = null; |
| } |
| package_config.Package? packageForLanguageVersion; |
| if (fileUri == null) { |
| switch (uri.scheme) { |
| case "package": |
| case "dart": |
| fileUri = target.translateUri(uri) ?? |
| new Uri( |
| scheme: untranslatableUriScheme, |
| path: Uri.encodeComponent("$uri")); |
| if (uri.isScheme("package")) { |
| packageForLanguageVersion = target.uriTranslator.getPackage(uri); |
| } else { |
| packageForLanguageVersion = |
| target.uriTranslator.packages.packageOf(fileUri); |
| } |
| break; |
| |
| default: |
| fileUri = uri; |
| packageForLanguageVersion = |
| target.uriTranslator.packages.packageOf(fileUri); |
| break; |
| } |
| } else { |
| packageForLanguageVersion = |
| target.uriTranslator.packages.packageOf(fileUri); |
| } |
| LanguageVersion? packageLanguageVersion; |
| Uri? packageUri; |
| Message? packageLanguageVersionProblem; |
| if (packageForLanguageVersion != null) { |
| Uri importUri = origin?.importUri ?? uri; |
| if (!importUri.isScheme('dart') && !importUri.isScheme('package')) { |
| packageUri = |
| new Uri(scheme: 'package', path: packageForLanguageVersion.name); |
| } |
| if (packageForLanguageVersion.languageVersion != null) { |
| if (packageForLanguageVersion.languageVersion |
| is package_config.InvalidLanguageVersion) { |
| // Coverage-ignore-block(suite): Not run. |
| packageLanguageVersionProblem = |
| messageLanguageVersionInvalidInDotPackages; |
| packageLanguageVersion = new InvalidLanguageVersion( |
| fileUri, 0, noLength, target.currentSdkVersion, false); |
| } else { |
| Version version = new Version( |
| packageForLanguageVersion.languageVersion!.major, |
| packageForLanguageVersion.languageVersion!.minor); |
| if (version > target.currentSdkVersion) { |
| // Coverage-ignore-block(suite): Not run. |
| packageLanguageVersionProblem = |
| templateLanguageVersionTooHigh.withArguments( |
| target.currentSdkVersion.major, |
| target.currentSdkVersion.minor); |
| packageLanguageVersion = new InvalidLanguageVersion( |
| fileUri, 0, noLength, target.currentSdkVersion, false); |
| } else if (version < target.leastSupportedVersion) { |
| packageLanguageVersionProblem = |
| templateLanguageVersionTooLow.withArguments( |
| target.leastSupportedVersion.major, |
| target.leastSupportedVersion.minor); |
| packageLanguageVersion = new InvalidLanguageVersion( |
| fileUri, 0, noLength, target.leastSupportedVersion, false); |
| } else { |
| packageLanguageVersion = new ImplicitLanguageVersion(version); |
| } |
| } |
| } |
| } |
| packageLanguageVersion ??= |
| new ImplicitLanguageVersion(target.currentSdkVersion); |
| |
| originImportUri ??= uri; |
| SourceLibraryBuilder libraryBuilder = createLibraryBuilder( |
| importUri: uri, |
| fileUri: fileUri, |
| packageUri: packageUri, |
| originImportUri: originImportUri, |
| packageLanguageVersion: packageLanguageVersion, |
| origin: origin, |
| referencesFromIndex: referencesFromIndex, |
| referenceIsPartOwner: referenceIsPartOwner, |
| isAugmentation: isAugmentation, |
| isPatch: isPatch); |
| SourceCompilationUnit compilationUnit = libraryBuilder.compilationUnit; |
| if (packageLanguageVersionProblem != null) { |
| compilationUnit.addPostponedProblem( |
| packageLanguageVersionProblem, 0, noLength, compilationUnit.fileUri); |
| } |
| |
| if (addAsRoot) { |
| roots.add(uri); |
| } |
| |
| _checkForDartCore(uri, libraryBuilder, compilationUnit); |
| |
| if (target.backendTarget.mayDefineRestrictedType(originImportUri)) { |
| // Coverage-ignore-block(suite): Not run. |
| libraryBuilder.mayImplementRestrictedTypes = true; |
| } |
| if (uri.isScheme("dart") && originImportUri.isScheme("dart")) { |
| // We only read the patch files if the [compilationUnit] is loaded as a |
| // dart: library (through [uri]) and is considered a dart: library |
| // (through [originImportUri]). |
| // |
| // This is to avoid reading patches and when reading dart: parts, and to |
| // avoid reading patches of non-dart: libraries that claim to be a part of |
| // a dart: library. |
| target.readPatchFiles(libraryBuilder, compilationUnit, originImportUri); |
| } |
| _unparsedLibraries.addLast(compilationUnit); |
| |
| return compilationUnit; |
| } |
| |
| DillLibraryBuilder? _lookupDillLibraryBuilder(Uri uri) { |
| DillLibraryBuilder? libraryBuilder = |
| target.dillTarget.loader.lookupLibraryBuilder(uri); |
| if (libraryBuilder != null) { |
| _checkDillLibraryBuilderNnbdMode(libraryBuilder); |
| _checkForDartCore( |
| uri, libraryBuilder, libraryBuilder.mainCompilationUnit); |
| } |
| return libraryBuilder; |
| } |
| |
| void _checkDillLibraryBuilderNnbdMode(DillLibraryBuilder libraryBuilder) { |
| NonNullableByDefaultCompiledMode libraryMode = |
| libraryBuilder.library.nonNullableByDefaultCompiledMode; |
| if (libraryMode == NonNullableByDefaultCompiledMode.Invalid) { |
| // Coverage-ignore-block(suite): Not run. |
| registerNnbdMismatchLibrary( |
| libraryBuilder, messageInvalidNnbdDillLibrary); |
| } else { |
| switch (nnbdMode) { |
| case NnbdMode.Weak: |
| if (libraryMode != NonNullableByDefaultCompiledMode.Weak) { |
| registerNnbdMismatchLibrary( |
| libraryBuilder, messageWeakWithStrongDillLibrary); |
| } |
| break; |
| case NnbdMode.Strong: |
| if (libraryMode != NonNullableByDefaultCompiledMode.Strong) { |
| registerNnbdMismatchLibrary( |
| libraryBuilder, messageStrongWithWeakDillLibrary); |
| } |
| break; |
| } |
| } |
| } |
| |
| void _markDartLibraries( |
| Uri uri, LibraryBuilder libraryBuilder, CompilationUnit compilationUnit) { |
| if (uri.isScheme("dart")) { |
| if (uri.path == "core") { |
| _coreLibrary = libraryBuilder; |
| _coreLibraryCompilationUnit = compilationUnit; |
| } else if (uri.path == "typed_data") { |
| typedDataLibrary = libraryBuilder; |
| } |
| } |
| } |
| |
| void _checkForDartCore( |
| Uri uri, LibraryBuilder libraryBuilder, CompilationUnit compilationUnit) { |
| _markDartLibraries(uri, libraryBuilder, compilationUnit); |
| |
| // TODO(johnniwinther): If we save the created library in [_builders] |
| // here, i.e. before calling `target.loadExtraRequiredLibraries` below, |
| // the order of the libraries change, making `dart:core` come before the |
| // required arguments. Currently [DillLoader.appendLibrary] one works |
| // when this is not the case. |
| if (_coreLibrary == libraryBuilder) { |
| target.loadExtraRequiredLibraries(this); |
| } |
| } |
| |
| /// Look up a library builder by the [uri], or if such doesn't exist, create |
| /// one. The canonical URI of the library is [uri], and its actual location is |
| /// [fileUri]. |
| /// |
| /// Canonical URIs have schemes like "dart", or "package", and the actual |
| /// location is often a file URI. |
| /// |
| /// The [accessor] is the library that's trying to import, export, or include |
| /// as part [uri], and [charOffset] is the location of the corresponding |
| /// directive. If [accessor] isn't allowed to access [uri], it's a |
| /// compile-time error. |
| CompilationUnit read(Uri uri, int charOffset, |
| {Uri? fileUri, |
| required CompilationUnit accessor, |
| Uri? originImportUri, |
| SourceLibraryBuilder? origin, |
| IndexedLibrary? referencesFromIndex, |
| bool? referenceIsPartOwner, |
| bool isAugmentation = false, |
| bool isPatch = false}) { |
| CompilationUnit libraryBuilder = _read(uri, |
| fileUri: fileUri, |
| originImportUri: originImportUri, |
| origin: origin, |
| referencesFromIndex: referencesFromIndex, |
| referenceIsPartOwner: referenceIsPartOwner, |
| isAugmentation: isAugmentation, |
| isPatch: isPatch, |
| addAsRoot: false); |
| libraryBuilder.recordAccess( |
| accessor, charOffset, noLength, accessor.fileUri); |
| if (!_hasLibraryAccess(imported: uri, importer: accessor.importUri) && |
| !accessor.isAugmenting) { |
| accessor.addProblem(messagePlatformPrivateLibraryAccess, charOffset, |
| noLength, accessor.fileUri); |
| } |
| return libraryBuilder; |
| } |
| |
| /// Reads the library [uri] as an entry point. This is used for reading the |
| /// entry point library of a script or the explicitly mention libraries of |
| /// a modular or incremental compilation. |
| /// |
| /// This differs from [read] in that there is no accessor library, meaning |
| /// that access to platform private libraries cannot be granted. |
| CompilationUnit readAsEntryPoint( |
| Uri uri, { |
| Uri? fileUri, |
| IndexedLibrary? referencesFromIndex, |
| }) { |
| CompilationUnit libraryBuilder = _read(uri, |
| fileUri: fileUri, |
| referencesFromIndex: referencesFromIndex, |
| addAsRoot: true, |
| isAugmentation: false, |
| isPatch: false); |
| // TODO(johnniwinther): Avoid using the first library, if present, as the |
| // accessor of [libraryBuilder]. Currently the incremental compiler doesn't |
| // handle errors reported without an accessor, since the messages are not |
| // associated with a library. This currently has the side effect that |
| // the first library is the accessor of itself. |
| CompilationUnit? firstLibrary = rootCompilationUnit; |
| if (firstLibrary != null) { |
| libraryBuilder.recordAccess( |
| firstLibrary, -1, noLength, firstLibrary.fileUri); |
| } |
| if (!_hasLibraryAccess(imported: uri, importer: firstLibrary?.importUri)) { |
| // Coverage-ignore-block(suite): Not run. |
| if (firstLibrary != null) { |
| firstLibrary.addProblem(messagePlatformPrivateLibraryAccess, -1, |
| noLength, firstLibrary.importUri); |
| } else { |
| addProblem(messagePlatformPrivateLibraryAccess, -1, noLength, null); |
| } |
| } |
| return libraryBuilder; |
| } |
| |
| bool _hasLibraryAccess({required Uri imported, required Uri? importer}) { |
| if (imported.isScheme("dart") && imported.path.startsWith("_")) { |
| if (importer == null) { |
| return false; |
| } else { |
| return target.backendTarget |
| .allowPlatformPrivateLibraryAccess(importer, imported); |
| } |
| } |
| return true; |
| } |
| |
| CompilationUnit _read(Uri uri, |
| {required Uri? fileUri, |
| Uri? originImportUri, |
| SourceLibraryBuilder? origin, |
| required IndexedLibrary? referencesFromIndex, |
| bool? referenceIsPartOwner, |
| required bool isAugmentation, |
| required bool isPatch, |
| required bool addAsRoot}) { |
| CompilationUnit? compilationUnit = _compilationUnits[uri]; |
| if (compilationUnit == null) { |
| if (target.dillTarget.isLoaded) { |
| compilationUnit = _lookupDillLibraryBuilder(uri)?.mainCompilationUnit; |
| } |
| if (compilationUnit == null) { |
| compilationUnit = _createSourceCompilationUnit( |
| uri: uri, |
| fileUri: fileUri, |
| originImportUri: originImportUri, |
| origin: origin, |
| referencesFromIndex: referencesFromIndex, |
| referenceIsPartOwner: referenceIsPartOwner, |
| isAugmentation: isAugmentation, |
| isPatch: isPatch, |
| addAsRoot: addAsRoot); |
| } |
| _compilationUnits[uri] = compilationUnit; |
| } |
| return compilationUnit; |
| } |
| |
| void _ensureCoreLibrary() { |
| if (_coreLibrary == null) { |
| readAsEntryPoint(Uri.parse("dart:core")); |
| // TODO(askesc): When all backends support set literals, we no longer |
| // need to index dart:collection, as it is only needed for desugaring of |
| // const sets. We can remove it from this list at that time. |
| readAsEntryPoint(Uri.parse("dart:collection")); |
| assert(_coreLibrary != null); |
| } |
| } |
| |
| Future<Null> buildBodies(List<SourceLibraryBuilder> libraryBuilders) async { |
| assert(_coreLibrary != null); |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| currentUriForCrashReporting = |
| new UriOffset(library.importUri, TreeNode.noOffset); |
| await buildBody(library); |
| } |
| // Workaround: This will return right away but avoid a "semi leak" |
| // where the latest library is saved in a context somewhere. |
| await buildBody(null); |
| currentUriForCrashReporting = null; |
| logSummary(templateSourceBodySummary); |
| } |
| |
| void logSummary(Template<SummaryTemplate> template) { |
| ticker.log( |
| // Coverage-ignore(suite): Not run. |
| (Duration elapsed, Duration sinceStart) { |
| int libraryCount = 0; |
| for (CompilationUnit library in compilationUnits) { |
| if (library.loader == this) { |
| libraryCount++; |
| } |
| } |
| double ms = elapsed.inMicroseconds / Duration.microsecondsPerMillisecond; |
| Message message = template.withArguments( |
| libraryCount, byteCount, ms, byteCount / ms, ms / libraryCount); |
| print("$sinceStart: ${message.problemMessage}"); |
| }); |
| } |
| |
| /// Register [message] as a problem with a severity determined by the |
| /// intrinsic severity of the message. |
| @override |
| FormattedMessage? addProblem( |
| Message message, int charOffset, int length, Uri? fileUri, |
| {bool wasHandled = false, |
| List<LocatedMessage>? context, |
| Severity? severity, |
| bool problemOnLibrary = false, |
| List<Uri>? involvedFiles}) { |
| return addMessage(message, charOffset, length, fileUri, severity, |
| wasHandled: wasHandled, |
| context: context, |
| problemOnLibrary: problemOnLibrary, |
| involvedFiles: involvedFiles); |
| } |
| |
| /// All messages reported by the compiler (errors, warnings, etc.) are routed |
| /// through this method. |
| /// |
| /// Returns a FormattedMessage if the message is new, that is, not previously |
| /// reported. This is important as some parser errors may be reported up to |
| /// three times by `OutlineBuilder`, `DietListener`, and `BodyBuilder`. |
| /// If the message is not new, [null] is reported. |
| /// |
| /// If [severity] is `Severity.error`, the message is added to |
| /// [handledErrors] if [wasHandled] is true or to [unhandledErrors] if |
| /// [wasHandled] is false. |
| FormattedMessage? addMessage(Message message, int charOffset, int length, |
| Uri? fileUri, Severity? severity, |
| {bool wasHandled = false, |
| List<LocatedMessage>? context, |
| bool problemOnLibrary = false, |
| List<Uri>? involvedFiles}) { |
| assert( |
| fileUri != missingUri, "Message unexpectedly reported on missing uri."); |
| severity ??= message.code.severity; |
| if (severity == Severity.ignored) return null; |
| String trace = """ |
| message: ${message.problemMessage} |
| charOffset: $charOffset |
| fileUri: $fileUri |
| severity: $severity |
| """; |
| if (!seenMessages.add(trace)) return null; |
| if (message.code.severity == Severity.error) { |
| _hasSeenError = true; |
| } |
| if (message.code.severity == Severity.context) { |
| internalProblem( |
| templateInternalProblemContextSeverity |
| .withArguments(message.code.name), |
| charOffset, |
| fileUri); |
| } |
| target.context.report( |
| fileUri != null |
| ? message.withLocation(fileUri, charOffset, length) |
| : |
| // Coverage-ignore(suite): Not run. |
| message.withoutLocation(), |
| severity, |
| context: context, |
| involvedFiles: involvedFiles); |
| if (severity == Severity.error) { |
| (wasHandled ? handledErrors : unhandledErrors).add(fileUri != null |
| ? message.withLocation(fileUri, charOffset, length) |
| : |
| // Coverage-ignore(suite): Not run. |
| message.withoutLocation()); |
| } |
| FormattedMessage formattedMessage = target.createFormattedMessage( |
| message, charOffset, length, fileUri, context, severity, |
| involvedFiles: involvedFiles); |
| if (!problemOnLibrary) { |
| allComponentProblems.add(formattedMessage); |
| } |
| return formattedMessage; |
| } |
| |
| MemberBuilder getDuplicatedFieldInitializerError() { |
| return target.getDuplicatedFieldInitializerError(this); |
| } |
| |
| MemberBuilder getNativeAnnotation() => target.getNativeAnnotation(this); |
| |
| BodyBuilder createBodyBuilderForOutlineExpression( |
| SourceLibraryBuilder library, |
| BodyBuilderContext bodyBuilderContext, |
| LookupScope scope, |
| Uri fileUri, |
| {LocalScope? formalParameterScope}) { |
| return new BodyBuilder.forOutlineExpression( |
| library, bodyBuilderContext, scope, fileUri, |
| formalParameterScope: formalParameterScope); |
| } |
| |
| NnbdMode get nnbdMode => target.context.options.nnbdMode; |
| |
| CoreTypes get coreTypes { |
| assert(_coreTypes != null, "CoreTypes has not been computed."); |
| return _coreTypes!; |
| } |
| |
| ClassHierarchy get hierarchy => _hierarchy!; |
| |
| void set hierarchy(ClassHierarchy? value) { |
| if (_hierarchy != value) { |
| _hierarchy = value; |
| _typeEnvironment = null; |
| } |
| } |
| |
| TypeEnvironment get typeEnvironment { |
| return _typeEnvironment ??= new TypeEnvironment(coreTypes, hierarchy); |
| } |
| |
| final InferableTypes inferableTypes = new InferableTypes(); |
| |
| TypeInferenceEngineImpl get typeInferenceEngine => _typeInferenceEngine!; |
| |
| ClassHierarchyBuilder get hierarchyBuilder => _hierarchyBuilder!; |
| |
| ClassMembersBuilder get membersBuilder => _membersBuilder!; |
| |
| Template<SummaryTemplate> get outlineSummaryTemplate => |
| templateSourceOutlineSummary; |
| |
| /// The [SourceCompilationUnit]s for the `dart:` libraries that are not |
| /// available. |
| /// |
| /// We special-case the errors for accessing these libraries and report |
| /// it at the end of [buildOutlines] to ensure that all import paths are |
| /// part of the error message. |
| Set<SourceCompilationUnit> _unavailableDartLibraries = {}; |
| |
| Future<Token> tokenize(SourceCompilationUnit compilationUnit, |
| {bool suppressLexicalErrors = false}) async { |
| target.benchmarker |
| // Coverage-ignore(suite): Not run. |
| ?.beginSubdivide(BenchmarkSubdivides.tokenize); |
| Uri fileUri = compilationUnit.fileUri; |
| |
| // Lookup the file URI in the cache. |
| Uint8List? bytes = sourceBytes[fileUri]; |
| |
| if (bytes == null) { |
| // Error recovery. |
| if (fileUri.isScheme(untranslatableUriScheme)) { |
| Uri importUri = compilationUnit.importUri; |
| if (importUri.isScheme('dart')) { |
| // We report this error later in [buildOutlines]. |
| _unavailableDartLibraries.add(compilationUnit); |
| } else { |
| compilationUnit.addProblemAtAccessors( |
| templateUntranslatableUri.withArguments(importUri)); |
| } |
| bytes = synthesizeSourceForMissingFile(importUri, null); |
| } else if (!fileUri.hasScheme) { |
| // Coverage-ignore-block(suite): Not run. |
| target.benchmarker?.endSubdivide(); |
| return internalProblem( |
| templateInternalProblemUriMissingScheme.withArguments(fileUri), |
| -1, |
| compilationUnit.importUri); |
| } else if (fileUri.isScheme(MALFORMED_URI_SCHEME)) { |
| compilationUnit.addProblemAtAccessors(messageExpectedUri); |
| bytes = synthesizeSourceForMissingFile(compilationUnit.importUri, null); |
| } |
| if (bytes != null) { |
| Uint8List zeroTerminatedBytes = new Uint8List(bytes.length + 1); |
| zeroTerminatedBytes.setRange(0, bytes.length, bytes); |
| bytes = zeroTerminatedBytes; |
| sourceBytes[fileUri] = bytes; |
| } |
| } |
| |
| if (bytes == null) { |
| // If it isn't found in the cache, read the file read from the file |
| // system. |
| List<int> rawBytes; |
| try { |
| rawBytes = await fileSystem.entityForUri(fileUri).readAsBytes(); |
| } on FileSystemException catch (e) { |
| Message message = templateCantReadFile.withArguments( |
| fileUri, target.context.options.osErrorMessage(e.message)); |
| compilationUnit.addProblemAtAccessors(message); |
| rawBytes = |
| synthesizeSourceForMissingFile(compilationUnit.importUri, message); |
| } |
| Uint8List zeroTerminatedBytes = new Uint8List(rawBytes.length + 1); |
| zeroTerminatedBytes.setRange(0, rawBytes.length, rawBytes); |
| bytes = zeroTerminatedBytes; |
| sourceBytes[fileUri] = bytes; |
| byteCount += rawBytes.length; |
| } |
| |
| ScannerResult result = scan(bytes, |
| includeComments: includeComments, |
| configuration: new ScannerConfiguration( |
| enableTripleShift: target.isExperimentEnabledInLibraryByVersion( |
| ExperimentalFlag.tripleShift, |
| compilationUnit.importUri, |
| compilationUnit.packageLanguageVersion.version), |
| enableExtensionMethods: |
| target.isExperimentEnabledInLibraryByVersion( |
| ExperimentalFlag.extensionMethods, |
| compilationUnit.importUri, |
| compilationUnit.packageLanguageVersion.version), |
| enableNonNullable: target.isExperimentEnabledInLibraryByVersion( |
| ExperimentalFlag.nonNullable, |
| compilationUnit.importUri, |
| compilationUnit.packageLanguageVersion.version), |
| forAugmentationLibrary: compilationUnit.forAugmentationLibrary), |
| languageVersionChanged: |
| (Scanner scanner, LanguageVersionToken version) { |
| if (!suppressLexicalErrors) { |
| compilationUnit.registerExplicitLanguageVersion( |
| new Version(version.major, version.minor), |
| offset: version.offset, |
| length: version.length); |
| } |
| scanner.configuration = new ScannerConfiguration( |
| enableTripleShift: |
| compilationUnit.libraryFeatures.tripleShift.isEnabled, |
| enableExtensionMethods: |
| compilationUnit.libraryFeatures.extensionMethods.isEnabled, |
| enableNonNullable: true); |
| }); |
| Token token = result.tokens; |
| if (!suppressLexicalErrors) { |
| List<int> source = getSource(bytes); |
| |
| /// We use the [importUri] of the created [Library] and not the |
| /// [importUri] of the [LibraryBuilder] since it might be an augmentation |
| /// library which is not directly part of the output. |
| Uri importUri = compilationUnit.importUri; |
| if (compilationUnit.isAugmenting) { |
| // For patch libraries we create a "fake" import uri. |
| // We cannot use the import uri from the augmented library because |
| // several different files would then have the same import uri, |
| // and the VM does not support that. Also, what would, for instance, |
| // setting a breakpoint on line 42 of some import uri mean, if the uri |
| // represented several files? |
| if (compilationUnit.forPatchLibrary) { |
| // TODO(johnniwinther): Use augmentation-like solution for patching. |
| List<String> newPathSegments = |
| new List<String>.of(compilationUnit.originImportUri.pathSegments); |
| newPathSegments.add(compilationUnit.fileUri.pathSegments.last); |
| newPathSegments[0] = "${newPathSegments[0]}-patch"; |
| importUri = compilationUnit.originImportUri |
| .replace(pathSegments: newPathSegments); |
| } |
| } |
| target.addSourceInformation( |
| importUri, compilationUnit.fileUri, result.lineStarts, source); |
| } |
| compilationUnit.issuePostponedProblems(); |
| compilationUnit.markLanguageVersionFinal(); |
| while (token is ErrorToken) { |
| if (!suppressLexicalErrors) { |
| ErrorToken error = token; |
| compilationUnit.addProblem(error.assertionMessage, |
| offsetForToken(token), lengthForToken(token), fileUri); |
| } |
| token = token.next!; |
| } |
| target.benchmarker |
| // Coverage-ignore(suite): Not run. |
| ?.endSubdivide(); |
| return token; |
| } |
| |
| Uint8List synthesizeSourceForMissingFile(Uri uri, Message? message) { |
| return utf8.encode(switch ("$uri") { |
| "dart:core" => defaultDartCoreSource, |
| "dart:async" => defaultDartAsyncSource, |
| "dart:collection" => defaultDartCollectionSource, |
| "dart:_internal" => defaultDartInternalSource, |
| "dart:typed_data" => defaultDartTypedDataSource, |
| _ => message == null ? "" : "/* ${message.problemMessage} */", |
| }); |
| } |
| |
| bool hasInvalidNnbdModeLibrary = false; |
| |
| Map<LibraryBuilder, Message>? _nnbdMismatchLibraries; |
| |
| void registerNnbdMismatchLibrary( |
| LibraryBuilder libraryBuilder, Message message) { |
| _nnbdMismatchLibraries ??= {}; |
| _nnbdMismatchLibraries![libraryBuilder] = message; |
| hasInvalidNnbdModeLibrary = true; |
| } |
| |
| void registerConstructorToBeInferred( |
| Member member, SourceConstructorBuilder builder) { |
| _typeInferenceEngine!.toBeInferred[member] = builder; |
| } |
| |
| void registerTypeDependency(Member member, TypeDependency typeDependency) { |
| _typeInferenceEngine!.typeDependencies[member] = typeDependency; |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| /// Registers the [compilationUnit] as unparsed with the given [source] code. |
| /// |
| /// This is used for creating synthesized augmentation libraries. |
| void registerUnparsedLibrarySource( |
| SourceCompilationUnit compilationUnit, String source) { |
| List<int> codeUnits = source.codeUnits; |
| Uint8List bytes = new Uint8List(codeUnits.length + 1); |
| bytes.setRange(0, codeUnits.length, codeUnits); |
| sourceBytes[compilationUnit.fileUri] = bytes; |
| _unparsedLibraries.addLast(compilationUnit); |
| } |
| |
| /// Runs the [OutlineBuilder] on the source of all [_unparsedLibraries]. |
| Future<void> buildOutlines() async { |
| _ensureCoreLibrary(); |
| while (_unparsedLibraries.isNotEmpty) { |
| SourceCompilationUnit compilationUnit = _unparsedLibraries.removeFirst(); |
| currentUriForCrashReporting = |
| new UriOffset(compilationUnit.importUri, TreeNode.noOffset); |
| await buildOutline(compilationUnit); |
| } |
| currentUriForCrashReporting = null; |
| logSummary(outlineSummaryTemplate); |
| if (_nnbdMismatchLibraries != null) { |
| for (MapEntry<LibraryBuilder, Message> entry |
| in _nnbdMismatchLibraries!.entries) { |
| addProblem(entry.value, -1, noLength, entry.key.fileUri); |
| } |
| _nnbdMismatchLibraries = null; |
| } |
| if (_unavailableDartLibraries.isNotEmpty) { |
| CompilationUnit? rootLibrary = rootCompilationUnit; |
| LoadedLibraries? loadedLibraries; |
| for (SourceCompilationUnit compilationUnit in _unavailableDartLibraries) { |
| List<LocatedMessage>? context; |
| Uri importUri = compilationUnit.importUri; |
| Message message = |
| templateUnavailableDartLibrary.withArguments(importUri); |
| if (rootLibrary != null) { |
| loadedLibraries ??= |
| new LoadedLibrariesImpl(rootLibrary, compilationUnits); |
| Set<String> importChain = computeImportChainsFor( |
| rootLibrary.importUri, loadedLibraries, importUri, |
| verbose: false); |
| Set<String> verboseImportChain = computeImportChainsFor( |
| rootLibrary.importUri, loadedLibraries, importUri, |
| verbose: true); |
| if (importChain.isNotEmpty) { |
| if (importChain.containsAll(verboseImportChain)) { |
| context = [ |
| templateImportChainContextSimple |
| .withArguments(compilationUnit.importUri, |
| importChain.map((part) => ' $part\n').join()) |
| .withoutLocation(), |
| ]; |
| } else { |
| context = [ |
| templateImportChainContext |
| .withArguments( |
| compilationUnit.importUri, |
| importChain.map((part) => ' $part\n').join(), |
| verboseImportChain.map((part) => ' $part\n').join()) |
| .withoutLocation(), |
| ]; |
| } |
| } |
| } |
| // We only include the [context] on the first library access. |
| if (compilationUnit.accessors.isEmpty) { |
| // Coverage-ignore-block(suite): Not run. |
| // This is the entry point library, and nobody access it directly. So |
| // we need to report a problem. |
| addProblem(message, -1, 1, null, context: context); |
| } else { |
| LibraryAccess access = compilationUnit.accessors.first; |
| access.accessor.addProblem( |
| message, access.charOffset, access.length, access.fileUri, |
| context: context); |
| } |
| } |
| // All subsequent library accesses are reported here without the context |
| // message. |
| for (SourceCompilationUnit compilationUnit in _unavailableDartLibraries) { |
| Uri importUri = compilationUnit.importUri; |
| Message message = |
| templateUnavailableDartLibrary.withArguments(importUri); |
| |
| if (compilationUnit.accessors.length > 1) { |
| for (LibraryAccess access in compilationUnit.accessors) { |
| access.accessor.addProblem( |
| message, access.charOffset, access.length, access.fileUri); |
| } |
| } |
| // Mark the library with an access problem so that it will be marked |
| // as synthetic and so that subsequent accesses will be reported. |
| compilationUnit.accessProblem ??= message; |
| } |
| _unavailableDartLibraries.clear(); |
| } |
| } |
| |
| List<int> getSource(List<int> bytes) { |
| // 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); |
| } |
| // Coverage-ignore(suite): Not run. |
| return bytes.sublist(0, bytes.length - 1); |
| } |
| |
| Future<Null> buildOutline(SourceCompilationUnit compilationUnit) async { |
| Token tokens = await tokenize(compilationUnit); |
| OutlineBuilder listener = compilationUnit.createOutlineBuilder(); |
| new ClassMemberParser(listener, |
| allowPatterns: compilationUnit.libraryFeatures.patterns.isEnabled) |
| .parseUnit(tokens); |
| } |
| |
| /// Builds all the method bodies found in the given [library]. |
| Future<Null> buildBody(SourceLibraryBuilder? library) async { |
| // [library] is only nullable so we can call this a "dummy-time" to get rid |
| // of a semi-leak. |
| if (library == null) return; |
| Iterable<SourceLibraryBuilder>? augmentationLibraries = |
| library.augmentationLibraries; |
| if (augmentationLibraries != null) { |
| for (SourceLibraryBuilder augmentationLibrary in augmentationLibraries) { |
| await buildBody(augmentationLibrary); |
| } |
| } |
| |
| // 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. |
| SourceCompilationUnit compilationUnit = library.compilationUnit; |
| Token tokens = await tokenize(compilationUnit, suppressLexicalErrors: true); |
| |
| if (target.benchmarker != null) { |
| // When benchmarking we do extra parsing on it's own to get a timing of |
| // how much time is spent on the actual parsing (as opposed to the |
| // building of what's parsed). |
| // NOTE: This runs the parser over the token stream meaning that any |
| // parser recovery rewriting the token stream will have happened once |
| // the "real" parsing is done. This in turn means that some errors |
| // (e.g. missing semi-colon) will not be issued when benchmarking. |
| { |
| // Coverage-ignore-block(suite): Not run. |
| target.benchmarker?.beginSubdivide( |
| BenchmarkSubdivides.body_buildBody_benchmark_specific_diet_parser); |
| DietParser parser = new DietParser(new ForwardingListener(), |
| allowPatterns: library.libraryFeatures.patterns.isEnabled); |
| parser.parseUnit(tokens); |
| target.benchmarker?.endSubdivide(); |
| } |
| { |
| // Coverage-ignore-block(suite): Not run. |
| target.benchmarker?.beginSubdivide( |
| BenchmarkSubdivides.body_buildBody_benchmark_specific_parser); |
| Parser parser = new Parser(new ForwardingListener(), |
| allowPatterns: library.libraryFeatures.patterns.isEnabled); |
| parser.parseUnit(tokens); |
| target.benchmarker?.endSubdivide(); |
| } |
| } |
| |
| DietListener listener = |
| createDietListener(library, compilationUnit.offsetMap); |
| DietParser parser = new DietParser(listener, |
| allowPatterns: library.libraryFeatures.patterns.isEnabled); |
| parser.parseUnit(tokens); |
| for (SourceCompilationUnit compilationUnit in library.parts) { |
| Token tokens = |
| await tokenize(compilationUnit, suppressLexicalErrors: true); |
| DietListener listener = |
| createDietListener(library, compilationUnit.offsetMap); |
| DietParser parser = new DietParser(listener, |
| allowPatterns: library.libraryFeatures.patterns.isEnabled); |
| parser.parseUnit(tokens); |
| } |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| Future<Expression> buildExpression( |
| SourceLibraryBuilder libraryBuilder, |
| String? enclosingClassOrExtension, |
| bool isClassInstanceMember, |
| FunctionNode parameters, |
| VariableDeclaration? extensionThis) async { |
| Token token = await tokenize(libraryBuilder.compilationUnit, |
| suppressLexicalErrors: false); |
| DietListener dietListener = createDietListener( |
| libraryBuilder, |
| // Expression compilation doesn't build an outline, and thus doesn't |
| // support members from source, so we provide an empty [DeclarationMap]. |
| new OffsetMap(libraryBuilder.fileUri)); |
| |
| Builder parent = libraryBuilder; |
| if (enclosingClassOrExtension != null) { |
| Builder? builder = dietListener.memberScope |
| .lookupGetable(enclosingClassOrExtension, -1, libraryBuilder.fileUri); |
| if (builder is TypeDeclarationBuilder) { |
| switch (builder) { |
| case ClassBuilder(): |
| parent = builder; |
| dietListener |
| ..currentDeclaration = builder |
| ..memberScope = new NameSpaceLookupScope( |
| builder.nameSpace, |
| ScopeKind.declaration, |
| "debugExpression in class $enclosingClassOrExtension", |
| parent: TypeParameterScope.fromList( |
| dietListener.memberScope, builder.typeVariables)); |
| case ExtensionBuilder(): |
| parent = builder; |
| dietListener |
| ..currentDeclaration = builder |
| ..memberScope = new NameSpaceLookupScope( |
| builder.nameSpace, |
| ScopeKind.declaration, |
| "debugExpression in extension $enclosingClassOrExtension", |
| // TODO(johnniwinther): Shouldn't type variables be in scope? |
| parent: dietListener.memberScope); |
| case ExtensionTypeDeclarationBuilder(): |
| // TODO(johnniwinther): Handle this case. |
| case TypeAliasBuilder(): |
| case NominalVariableBuilder(): |
| case StructuralVariableBuilder(): |
| case InvalidTypeDeclarationBuilder(): |
| case BuiltinTypeDeclarationBuilder(): |
| // TODO(johnniwinther): How should we handle this case? |
| case OmittedTypeDeclarationBuilder(): |
| } |
| } |
| } |
| SourceProcedureBuilder builder = new SourceProcedureBuilder( |
| /* metadata = */ null, |
| /* modifier flags = */ 0, |
| const ImplicitTypeBuilder(), |
| "debugExpr", |
| /* type variables = */ null, |
| /* formals = */ null, |
| ProcedureKind.Method, |
| libraryBuilder, |
| libraryBuilder.fileUri, |
| /* start char offset = */ 0, |
| /* char offset = */ 0, |
| /* open paren offset = */ -1, |
| /* end offset = */ -1, |
| /* procedure reference = */ null, |
| /* tear off reference = */ null, |
| AsyncMarker.Sync, |
| new NameScheme( |
| containerName: null, |
| containerType: ContainerType.Library, |
| isInstanceMember: false, |
| libraryName: libraryBuilder.libraryName)) |
| ..parent = parent; |
| BodyBuilder listener = dietListener.createListener( |
| new ExpressionCompilerProcedureBodyBuildContext(dietListener, builder, |
| isDeclarationInstanceMember: isClassInstanceMember, |
| inOutlineBuildingPhase: false, |
| inMetadata: false, |
| inConstFields: false), |
| dietListener.memberScope, |
| thisVariable: extensionThis); |
| builder.procedure.function = parameters..parent = builder.procedure; |
| for (VariableDeclaration variable in parameters.positionalParameters) { |
| listener.typeInferrer.assignedVariables.declare(variable); |
| } |
| |
| return listener.parseSingleExpression( |
| new Parser(listener, |
| useImplicitCreationExpression: useImplicitCreationExpressionInCfe, |
| allowPatterns: libraryBuilder.libraryFeatures.patterns.isEnabled), |
| token, |
| parameters); |
| } |
| |
| DietListener createDietListener( |
| SourceLibraryBuilder library, OffsetMap offsetMap) { |
| return new DietListener( |
| library, hierarchy, coreTypes, typeInferenceEngine, offsetMap); |
| } |
| |
| void resolveParts() { |
| Map<Uri, SourceCompilationUnit> parts = {}; |
| List<SourceLibraryBuilder> libraries = []; |
| List<SourceLibraryBuilder> sourceLibraries = []; |
| List<SourceLibraryBuilder> augmentationLibraries = []; |
| _compilationUnits.forEach((Uri uri, CompilationUnit compilationUnit) { |
| switch (compilationUnit) { |
| case SourceCompilationUnit(): |
| if (compilationUnit.isPart) { |
| parts[uri] = compilationUnit; |
| } else { |
| SourceLibraryBuilder sourceLibraryBuilder = |
| compilationUnit.createLibrary(); |
| if (compilationUnit.isAugmenting) { |
| // TODO(johnniwinther): Avoid creating a [SourceLibraryBuilder] |
| // for augmentation libraries. |
| augmentationLibraries.add(sourceLibraryBuilder); |
| } else { |
| sourceLibraries.add(sourceLibraryBuilder); |
| _loadedLibraryBuilders[uri] = sourceLibraryBuilder; |
| } |
| libraries.add(sourceLibraryBuilder); |
| } |
| case DillCompilationUnit(): |
| _loadedLibraryBuilders[uri] = compilationUnit.libraryBuilder; |
| } |
| }); |
| Set<Uri> usedParts = new Set<Uri>(); |
| for (SourceLibraryBuilder library in libraries) { |
| library.includeParts(usedParts); |
| } |
| for (MapEntry<Uri, SourceCompilationUnit> entry in parts.entries) { |
| Uri uri = entry.key; |
| SourceCompilationUnit part = entry.value; |
| if (usedParts.contains(uri)) { |
| _compilationUnits.remove(uri); |
| if (roots.contains(uri)) { |
| roots.remove(uri); |
| roots.add(part.partOfLibrary!.importUri); |
| } |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| SourceLibraryBuilder sourceLibraryBuilder = part.createLibrary(); |
| sourceLibraries.add(sourceLibraryBuilder); |
| _loadedLibraryBuilders[uri] = sourceLibraryBuilder; |
| } |
| } |
| ticker.logMs("Resolved parts"); |
| |
| for (SourceLibraryBuilder augmentationLibrary in augmentationLibraries) { |
| _compilationUnits.remove(augmentationLibrary.fileUri); |
| augmentationLibrary.origin.addAugmentationLibrary(augmentationLibrary); |
| } |
| _sourceLibraryBuilders = sourceLibraries; |
| assert( |
| _compilationUnits.values.every((compilationUnit) => |
| !(compilationUnit is SourceCompilationUnit && |
| compilationUnit.isAugmenting)), |
| // Coverage-ignore(suite): Not run. |
| "Augmentation library found in libraryBuilders: " + |
| _compilationUnits.values |
| .where((compilationUnit) => |
| !(compilationUnit is SourceCompilationUnit && |
| compilationUnit.isAugmenting)) |
| .join(', ') + |
| "."); |
| assert( |
| sourceLibraries.every((library) => !library.isAugmenting), |
| // Coverage-ignore(suite): Not run. |
| "Augmentation library found in sourceLibraryBuilders: " |
| "${sourceLibraries.where((library) => library.isAugmenting)}."); |
| assert( |
| _compilationUnits.values.every((compilationUnit) => |
| compilationUnit.loader != this || |
| sourceLibraries.contains(compilationUnit.libraryBuilder)), |
| // Coverage-ignore(suite): Not run. |
| "Source library not found in sourceLibraryBuilders:" + |
| _compilationUnits.values |
| .where((compilationUnit) => |
| compilationUnit.loader == this && |
| !sourceLibraries.contains(compilationUnit.libraryBuilder)) |
| .join(', ') + |
| "."); |
| ticker.logMs("Applied augmentations"); |
| } |
| |
| void buildScopes(Iterable<SourceLibraryBuilder> sourceLibraryBuilders) { |
| for (SourceLibraryBuilder sourceLibraryBuilder in sourceLibraryBuilders) { |
| sourceLibraryBuilder.buildScopes(coreLibrary); |
| } |
| ticker.logMs("Resolved scopes"); |
| } |
| |
| /// Compute library scopes for [libraryBuilders]. |
| void computeLibraryScopes(Iterable<LibraryBuilder> libraryBuilders) { |
| Set<LibraryBuilder> exporters = new Set<LibraryBuilder>(); |
| Set<LibraryBuilder> exportees = new Set<LibraryBuilder>(); |
| for (LibraryBuilder library in libraryBuilders) { |
| if (library.loader == this) { |
| SourceLibraryBuilder sourceLibrary = library as SourceLibraryBuilder; |
| sourceLibrary.buildInitialScopes(); |
| } |
| if (library.exporters.isNotEmpty) { |
| exportees.add(library); |
| for (Export exporter in library.exporters) { |
| exporters.add(exporter.exporter.libraryBuilder); |
| } |
| } |
| } |
| Set<SourceLibraryBuilder> both = new Set<SourceLibraryBuilder>(); |
| for (LibraryBuilder exported in exportees) { |
| if (exporters.contains(exported)) { |
| both.add(exported as SourceLibraryBuilder); |
| } |
| for (Export export in exported.exporters) { |
| exported.exportNameSpace |
| .filteredNameIterator( |
| includeDuplicates: false, includeAugmentations: false) |
| .forEach(export.addToExportScope); |
| } |
| } |
| bool wasChanged = false; |
| do { |
| wasChanged = false; |
| for (SourceLibraryBuilder exported in both) { |
| for (Export export in exported.exporters) { |
| NameIterator<Builder> iterator = exported.exportNameSpace |
| .filteredNameIterator( |
| includeDuplicates: false, includeAugmentations: false); |
| while (iterator.moveNext()) { |
| if (export.addToExportScope(iterator.name, iterator.current)) { |
| wasChanged = true; |
| } |
| } |
| } |
| } |
| } while (wasChanged); |
| for (LibraryBuilder library in libraryBuilders) { |
| if (library.loader == this) { |
| SourceLibraryBuilder sourceLibrary = library as SourceLibraryBuilder; |
| sourceLibrary.addImportsToScope(); |
| } |
| } |
| for (LibraryBuilder exportee in exportees) { |
| // TODO(ahe): Change how we track exporters. Currently, when a library |
| // (exporter) exports another library (exportee) we add a reference to |
| // exporter to exportee. This creates a reference in the wrong direction |
| // and can lead to memory leaks. |
| exportee.exporters.clear(); |
| } |
| ticker.logMs("Computed library scopes"); |
| } |
| |
| /// Resolve [NamedTypeBuilder]s in [libraryBuilders]. |
| void resolveTypes(Iterable<SourceLibraryBuilder> libraryBuilders) { |
| int typeCount = 0; |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| typeCount += library.resolveTypes(); |
| } |
| ticker.logMs("Resolved $typeCount types"); |
| } |
| |
| /// Computes which macro declarations that needs to be precompiled in order |
| /// to support macro application during compilation. |
| /// |
| /// If no macros need precompilation, `null` is returned. |
| NeededPrecompilations? computeMacroDeclarations() { |
| LibraryBuilder? macroLibraryBuilder = |
| lookupLoadedLibraryBuilder(macroLibraryUri); |
| if (macroLibraryBuilder == null) { |
| // The macro library might not be directly imported by the source |
| // libraries, so we look up in the dill loader as well. |
| macroLibraryBuilder = |
| target.dillTarget.loader.lookupLibraryBuilder(macroLibraryUri); |
| if (macroLibraryBuilder == null) { |
| return null; |
| } |
| } |
| |
| // Coverage-ignore-block(suite): Not run. |
| Builder? macroClassBuilder = |
| macroLibraryBuilder.lookupLocalMember(macroClassName); |
| if (macroClassBuilder is! ClassBuilder) { |
| // TODO(johnniwinther): Report this when the actual macro builder package |
| // exists. It should at least be a warning. |
| return null; |
| } |
| |
| _macroClassBuilder = macroClassBuilder; |
| if (retainDataForTesting) { |
| dataForTesting!.macroDeclarationData.macrosAreAvailable = true; |
| } |
| |
| /// Libraries containing macros that need compilation mapped to the |
| /// [ClassBuilder]s for the macro classes. |
| Map<Uri, List<ClassBuilder>> macroLibraries = {}; |
| |
| for (SourceLibraryBuilder libraryBuilder in sourceLibraryBuilders) { |
| Iterator<ClassBuilder> iterator = |
| libraryBuilder.localMembersIteratorOfType(); |
| while (iterator.moveNext()) { |
| ClassBuilder builder = iterator.current; |
| if (builder.isMacro) { |
| Uri libraryUri = builder.libraryBuilder.importUri; |
| if (target.context.options.runningPrecompilations |
| .contains(libraryUri)) { |
| // We are explicitly compiling this macro. |
| _macroDeclarations.add(builder); |
| } else if (!target.context.options.macroExecutor |
| .libraryIsRegistered(libraryUri)) { |
| (macroLibraries[libraryUri] ??= []).add(builder); |
| if (retainDataForTesting) { |
| (dataForTesting!.macroDeclarationData |
| .macroDeclarations[libraryUri] ??= []) |
| .add(builder.name); |
| } |
| } |
| } |
| } |
| } |
| |
| if (macroLibraries.isEmpty) { |
| return null; |
| } |
| |
| List<List<Uri>> computeCompilationSequence(Graph<Uri> libraryGraph, |
| {required bool Function(Uri) filter}) { |
| List<List<Uri>> stronglyConnectedComponents = |
| computeStrongComponents(libraryGraph); |
| |
| Graph<List<Uri>> strongGraph = |
| new StrongComponentGraph(libraryGraph, stronglyConnectedComponents); |
| List<List<List<Uri>>> componentLayers = |
| topologicalSort(strongGraph).layers; |
| List<List<Uri>> layeredComponents = []; |
| List<Uri> currentLayer = []; |
| for (List<List<Uri>> layer in componentLayers) { |
| bool declaresMacro = false; |
| for (List<Uri> component in layer) { |
| for (Uri uri in component) { |
| if (filter(uri)) continue; |
| if (macroLibraries.containsKey(uri)) { |
| declaresMacro = true; |
| } |
| currentLayer.add(uri); |
| } |
| } |
| if (declaresMacro) { |
| layeredComponents.add(currentLayer); |
| currentLayer = []; |
| } |
| } |
| if (currentLayer.isNotEmpty) { |
| layeredComponents.add(currentLayer); |
| } |
| return layeredComponents; |
| } |
| |
| Graph<Uri> graph = new BuilderGraph(_loadedLibraryBuilders); |
| |
| /// Libraries that are considered precompiled. These are libraries that are |
| /// either given as precompiled macro libraries, or libraries that these |
| /// depend upon. |
| // TODO(johnniwinther): Can we assume that the precompiled dills are |
| // self-contained? |
| Set<Uri> precompiledLibraries = {}; |
| |
| void addPrecompiledLibrary(Uri uri) { |
| if (precompiledLibraries.add(uri)) { |
| for (Uri neighbor in graph.neighborsOf(uri)) { |
| addPrecompiledLibrary(neighbor); |
| } |
| } |
| } |
| |
| for (CompilationUnit builder in _compilationUnits.values) { |
| if (builder.importUri.isScheme("dart") && !builder.isSynthetic) { |
| // Assume the platform is precompiled. |
| addPrecompiledLibrary(builder.importUri); |
| } else if (target.context.options.macroExecutor |
| .libraryIsRegistered(builder.importUri)) { |
| // The precompiled macros given are also precompiled. |
| assert( |
| !macroLibraries.containsKey(builder.importUri), |
| "Macro library ${builder.importUri} is only partially " |
| "precompiled."); |
| addPrecompiledLibrary(builder.importUri); |
| } |
| } |
| |
| bool isPrecompiledLibrary(Uri uri) => precompiledLibraries.contains(uri); |
| |
| List<List<Uri>> compilationSteps = |
| computeCompilationSequence(graph, filter: isPrecompiledLibrary); |
| if (retainDataForTesting) { |
| dataForTesting!.macroDeclarationData.compilationSequence = |
| compilationSteps; |
| } |
| |
| if (compilationSteps.length > 1) { |
| // We have at least 1 layer of macros that need to be precompiled before |
| // we can compile the program itself. |
| Map<Uri, Map<String, List<String>>> neededPrecompilations = {}; |
| for (int i = 0; i < compilationSteps.length - 1; i++) { |
| List<Uri> compilationStep = compilationSteps[i]; |
| for (Uri uri in compilationStep) { |
| List<ClassBuilder>? macroClasses = macroLibraries[uri]; |
| // [uri] might not itself declare any macros but instead a part of the |
| // libraries that macros depend upon. |
| if (macroClasses != null) { |
| Map<String, List<String>>? constructorMap; |
| for (ClassBuilder macroClass in macroClasses) { |
| List<String> constructors = []; |
| NameIterator<MemberBuilder> iterator = macroClass.nameSpace |
| .filteredConstructorNameIterator( |
| includeDuplicates: false, includeAugmentations: true); |
| while (iterator.moveNext()) { |
| constructors.add(iterator.name); |
| } |
| if (constructors.isNotEmpty) { |
| // TODO(johnniwinther): If there is no constructor here, it |
| // means the macro had no _explicit_ constructors. Since macro |
| // constructor are required to be const, this would be an error |
| // case. We need to handle that precompilation could result in |
| // errors like this. For this case we should probably add 'new' |
| // in case of [constructors] being empty in expectation of |
| // triggering the error during precompilation. |
| (constructorMap ??= {})[macroClass.name] = constructors; |
| } |
| } |
| if (constructorMap != null) { |
| neededPrecompilations[uri] = constructorMap; |
| } |
| } |
| } |
| if (neededPrecompilations.isNotEmpty) { |
| if (retainDataForTesting) { |
| dataForTesting!.macroDeclarationData.neededPrecompilations |
| .add(neededPrecompilations); |
| } |
| // We have found the first needed layer of precompilation. There might |
| // be more layers but we'll compute these at the next attempt at |
| // compilation, when this layer has been precompiled. |
| return new NeededPrecompilations(neededPrecompilations); |
| } |
| } |
| } |
| if (compilationSteps.isNotEmpty) { |
| for (List<Uri> compilationStep in compilationSteps) { |
| for (Uri uri in compilationStep) { |
| List<ClassBuilder>? macroClasses = macroLibraries[uri]; |
| if (macroClasses != null) { |
| // These macros are to be compiled during this (last) compilation |
| // step. |
| _macroDeclarations.addAll(macroClasses); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| Class? get macroClass => _macroClassBuilder |
| // Coverage-ignore(suite): Not run. |
| ?.cls; |
| |
| Future<MacroApplications?> computeMacroApplications() async { |
| if (injected.macroImplementation == null && _macroClassBuilder == null) { |
| return null; |
| } |
| |
| // Coverage-ignore-block(suite): Not run. |
| MacroApplications macroApplications = new MacroApplications( |
| this, |
| target.context.options.macroExecutor, |
| dataForTesting?.macroApplicationData); |
| macroApplications.computeLibrariesMacroApplicationData( |
| sourceLibraryBuilders, _macroDeclarations); |
| if (macroApplications.hasLoadableMacroIds) { |
| target.benchmarker?.beginSubdivide( |
| BenchmarkSubdivides.computeMacroApplications_macroExecutorProvider); |
| await macroApplications.loadMacroIds(target.benchmarker); |
| target.benchmarker?.endSubdivide(); |
| return macroApplications; |
| } |
| return null; |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| Future<void> computeAdditionalMacroApplications( |
| MacroApplications macroApplications, |
| Iterable<SourceLibraryBuilder> sourceLibraryBuilders) async { |
| macroApplications.computeLibrariesMacroApplicationData( |
| sourceLibraryBuilders, _macroDeclarations); |
| if (macroApplications.hasLoadableMacroIds) { |
| target.benchmarker?.beginSubdivide( |
| BenchmarkSubdivides.computeMacroApplications_macroExecutorProvider); |
| await macroApplications.loadMacroIds(target.benchmarker); |
| target.benchmarker?.endSubdivide(); |
| } |
| } |
| |
| void finishDeferredLoadTearoffs() { |
| int count = 0; |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| count += library.finishDeferredLoadTearOffs(); |
| } |
| ticker.logMs("Finished deferred load tearoffs $count"); |
| } |
| |
| void finishNoSuchMethodForwarders() { |
| int count = 0; |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| count += library.finishForwarders(); |
| } |
| ticker.logMs("Finished forwarders for $count procedures"); |
| } |
| |
| void resolveConstructors(List<SourceLibraryBuilder> libraryBuilders) { |
| int count = 0; |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| count += library.resolveConstructors(); |
| } |
| ticker.logMs("Resolved $count constructors"); |
| } |
| |
| List<DelayedDefaultValueCloner>? installTypedefTearOffs() { |
| List<DelayedDefaultValueCloner>? delayedDefaultValueCloners; |
| if (target.backendTarget.isTypedefTearOffLoweringEnabled) { |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| List<DelayedDefaultValueCloner>? libraryDelayedDefaultValueCloners = |
| library.installTypedefTearOffs(); |
| if (libraryDelayedDefaultValueCloners != null) { |
| (delayedDefaultValueCloners ??= []) |
| .addAll(libraryDelayedDefaultValueCloners); |
| } |
| } |
| } |
| return delayedDefaultValueCloners; |
| } |
| |
| void finishTypeVariables(Iterable<SourceLibraryBuilder> libraryBuilders, |
| ClassBuilder object, TypeBuilder dynamicType) { |
| Map<NominalVariableBuilder, SourceLibraryBuilder> |
| unboundTypeVariableBuilders = {}; |
| Map<StructuralVariableBuilder, SourceLibraryBuilder> |
| unboundFunctionTypeTypeVariableBuilders = {}; |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| library.collectUnboundTypeVariables( |
| unboundTypeVariableBuilders, unboundFunctionTypeTypeVariableBuilders); |
| } |
| |
| // Ensure that type parameters are built after their dependencies by sorting |
| // them topologically using references in bounds. |
| List<TypeVariableBuilder> sortedTypeVariables = |
| sortAllTypeVariablesTopologically([ |
| ...unboundFunctionTypeTypeVariableBuilders.keys, |
| ...unboundTypeVariableBuilders.keys |
| ]); |
| |
| for (TypeVariableBuilder builder in sortedTypeVariables) { |
| switch (builder) { |
| case NominalVariableBuilder(): |
| SourceLibraryBuilder? libraryBuilder = |
| unboundTypeVariableBuilders[builder]!; |
| libraryBuilder.checkTypeVariableDependencies([builder]); |
| builder.finish(libraryBuilder, object, dynamicType); |
| case StructuralVariableBuilder(): |
| SourceLibraryBuilder? libraryBuilder = |
| unboundFunctionTypeTypeVariableBuilders[builder]!; |
| libraryBuilder.checkTypeVariableDependencies([builder]); |
| builder.finish(libraryBuilder, object, dynamicType); |
| } |
| } |
| |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| library.processPendingNullabilities(); |
| } |
| |
| ticker.logMs("Resolved ${sortedTypeVariables.length} type-variable bounds"); |
| } |
| |
| /// Computes variances of type parameters on typedefs in [libraryBuilders]. |
| void computeVariances(Iterable<SourceLibraryBuilder> libraryBuilders) { |
| int count = 0; |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| count += library.computeVariances(); |
| } |
| ticker.logMs("Computed variances of $count type variables"); |
| } |
| |
| void computeDefaultTypes(TypeBuilder dynamicType, TypeBuilder nullType, |
| TypeBuilder bottomType, ClassBuilder objectClass) { |
| int count = 0; |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| count += library.computeDefaultTypes( |
| dynamicType, nullType, bottomType, objectClass); |
| } |
| ticker.logMs("Computed default types for $count type variables"); |
| } |
| |
| void finishNativeMethods() { |
| int count = 0; |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| count += library.finishNativeMethods(); |
| } |
| ticker.logMs("Finished $count native methods"); |
| } |
| |
| void buildBodyNodes() { |
| int count = 0; |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| count += library.buildBodyNodes(); |
| } |
| ticker.logMs("Finished $count augmentation methods"); |
| } |
| |
| /// Check that [objectClass] has no supertypes. Recover by removing any |
| /// found. |
| void checkObjectClassHierarchy(ClassBuilder objectClass) { |
| if (objectClass is SourceClassBuilder && |
| // Coverage-ignore(suite): Not run. |
| objectClass.libraryBuilder.loader == this) { |
| // Coverage-ignore-block(suite): Not run. |
| if (objectClass.supertypeBuilder != null) { |
| objectClass.supertypeBuilder = null; |
| objectClass.addProblem( |
| messageObjectExtends, objectClass.charOffset, noLength); |
| } |
| if (objectClass.interfaceBuilders != null) { |
| objectClass.addProblem( |
| messageObjectImplements, objectClass.charOffset, noLength); |
| objectClass.interfaceBuilders = null; |
| } |
| if (objectClass.mixedInTypeBuilder != null) { |
| objectClass.addProblem( |
| messageObjectMixesIn, objectClass.charOffset, noLength); |
| objectClass.mixedInTypeBuilder = null; |
| } |
| } |
| } |
| |
| /// Add classes and extension types defined in libraries in this |
| /// [SourceLoader] to [sourceClasses] and [sourceExtensionTypes]. |
| void collectSourceClasses(List<SourceClassBuilder> sourceClasses, |
| List<SourceExtensionTypeDeclarationBuilder> sourceExtensionTypes) { |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| library.collectSourceClassesAndExtensionTypes( |
| sourceClasses, sourceExtensionTypes); |
| } |
| } |
| |
| /// Returns lists of all class builders and of all extension type builders |
| /// declared in this loader. The classes and extension type are sorted |
| /// topologically, any cycles in the hierarchy are reported as errors, cycles |
| /// are broken. This means that the rest of the pipeline (including backends) |
| /// can assume that there are no hierarchy cycles. |
| (List<SourceClassBuilder>, List<SourceExtensionTypeDeclarationBuilder>) |
| handleHierarchyCycles(ClassBuilder objectClass) { |
| Set<ClassBuilder> denyListedClasses = new Set<ClassBuilder>(); |
| for (int i = 0; i < denylistedCoreClasses.length; i++) { |
| denyListedClasses.add(coreLibrary.lookupLocalMember( |
| denylistedCoreClasses[i], |
| required: true) as ClassBuilder); |
| } |
| ClassBuilder enumClass = |
| coreLibrary.lookupLocalMember("Enum", required: true) as ClassBuilder; |
| if (typedDataLibrary != null) { |
| for (int i = 0; i < denylistedTypedDataClasses.length; i++) { |
| // Allow the member to not exist. If it doesn't, nobody can extend it. |
| Builder? member = typedDataLibrary! |
| .lookupLocalMember(denylistedTypedDataClasses[i], required: false); |
| if (member != null) denyListedClasses.add(member as ClassBuilder); |
| } |
| } |
| |
| // Sort the classes topologically. |
| List<SourceClassBuilder> sourceClasses = []; |
| List<SourceExtensionTypeDeclarationBuilder> sourceExtensionTypes = []; |
| collectSourceClasses(sourceClasses, sourceExtensionTypes); |
| |
| _SourceClassGraph classGraph = |
| new _SourceClassGraph(sourceClasses, objectClass); |
| TopologicalSortResult<SourceClassBuilder> classResult = |
| topologicalSort(classGraph); |
| List<SourceClassBuilder> classes = classResult.sortedVertices; |
| |
| Map<ClassBuilder, ClassBuilder> classToBaseOrFinalSuperClass = {}; |
| for (SourceClassBuilder cls in classes) { |
| checkClassSupertypes(cls, classGraph.directSupertypeMap[cls]!, |
| denyListedClasses, enumClass); |
| _checkSupertypeClassModifiers(cls, classToBaseOrFinalSuperClass); |
| } |
| |
| List<SourceClassBuilder> classesWithCycles = classResult.cyclicVertices; |
| if (classesWithCycles.isNotEmpty) { |
| // Sort the classes to ensure consistent output. |
| classesWithCycles.sort(); |
| for (int i = 0; i < classesWithCycles.length; i++) { |
| SourceClassBuilder classBuilder = classesWithCycles[i]; |
| |
| // Ensure that the cycle is broken by removing superclass and |
| // implemented interfaces. |
| Class cls = classBuilder.cls; |
| cls.implementedTypes.clear(); |
| cls.supertype = null; |
| cls.mixedInType = null; |
| classBuilder.supertypeBuilder = |
| new NamedTypeBuilderImpl.fromTypeDeclarationBuilder( |
| objectClass, const NullabilityBuilder.omitted(), |
| instanceTypeVariableAccess: |
| InstanceTypeVariableAccessState.Unexpected); |
| classBuilder.interfaceBuilders = null; |
| classBuilder.mixedInTypeBuilder = null; |
| |
| classes.add(classBuilder); |
| // TODO(johnniwinther): Update the message for when a class depends on |
| // a cycle but does not depend on itself. |
| classBuilder.addProblem( |
| templateCyclicClassHierarchy |
| .withArguments(classBuilder.fullNameForErrors), |
| classBuilder.charOffset, |
| noLength); |
| } |
| } |
| |
| _SourceExtensionTypeGraph extensionTypeGraph = |
| new _SourceExtensionTypeGraph(sourceExtensionTypes); |
| TopologicalSortResult<SourceExtensionTypeDeclarationBuilder> |
| extensionTypeResult = topologicalSort(extensionTypeGraph); |
| List<SourceExtensionTypeDeclarationBuilder> extensionsTypes = |
| extensionTypeResult.sortedVertices; |
| |
| List<SourceExtensionTypeDeclarationBuilder> extensionTypesWithCycles = |
| extensionTypeResult.cyclicVertices; |
| if (extensionTypesWithCycles.isNotEmpty) { |
| // Sort the classes to ensure consistent output. |
| extensionTypesWithCycles.sort(); |
| for (int i = 0; i < extensionTypesWithCycles.length; i++) { |
| SourceExtensionTypeDeclarationBuilder extensionTypeBuilder = |
| extensionTypesWithCycles[i]; |
| |
| /// Ensure that the cycle is broken by removing implemented interfaces. |
| ExtensionTypeDeclaration extensionType = |
| extensionTypeBuilder.extensionTypeDeclaration; |
| extensionType.implements.clear(); |
| extensionTypeBuilder.interfaceBuilders = null; |
| extensionsTypes.add(extensionTypeBuilder); |
| // TODO(johnniwinther): Update the message for when an extension type |
| // depends on a cycle but does not depend on itself. |
| extensionTypeBuilder.addProblem( |
| templateCyclicClassHierarchy |
| .withArguments(extensionTypeBuilder.fullNameForErrors), |
| extensionTypeBuilder.charOffset, |
| noLength); |
| } |
| } |
| |
| ticker.logMs("Checked class hierarchy"); |
| return (classes, extensionsTypes); |
| } |
| |
| void _checkConstructorsForMixin( |
| SourceClassBuilder cls, ClassBuilder builder) { |
| Iterator<MemberBuilder> iterator = builder.nameSpace |
| .filteredConstructorIterator( |
| includeDuplicates: false, includeAugmentations: true); |
| while (iterator.moveNext()) { |
| MemberBuilder constructor = iterator.current; |
| if (constructor.isConstructor && !constructor.isSynthetic) { |
| cls.addProblem( |
| templateIllegalMixinDueToConstructors |
| .withArguments(builder.fullNameForErrors), |
| cls.charOffset, |
| noLength, |
| context: [ |
| templateIllegalMixinDueToConstructorsCause |
| .withArguments(builder.fullNameForErrors) |
| .withLocation( |
| constructor.fileUri!, constructor.charOffset, noLength) |
| ]); |
| } |
| } |
| } |
| |
| bool checkEnumSupertypeIsDenylisted(SourceClassBuilder cls) { |
| if (!cls.libraryBuilder.libraryFeatures.enhancedEnums.isEnabled) { |
| // Coverage-ignore-block(suite): Not run. |
| cls.addProblem( |
| templateEnumSupertypeOfNonAbstractClass.withArguments(cls.name), |
| cls.charOffset, |
| noLength); |
| return true; |
| } |
| return false; |
| } |
| |
| void checkClassSupertypes( |
| SourceClassBuilder cls, |
| Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypeMap, |
| Set<ClassBuilder> denyListedClasses, |
| ClassBuilder enumClass) { |
| // Check that the direct supertypes aren't deny-listed or enums. |
| List<TypeDeclarationBuilder?> directSupertypes = |
| directSupertypeMap.keys.toList(); |
| for (int i = 0; i < directSupertypes.length; i++) { |
| TypeDeclarationBuilder? supertype = directSupertypes[i]; |
| if (supertype is SourceEnumBuilder) { |
| // Coverage-ignore-block(suite): Not run. |
| cls.addProblem(templateExtendingEnum.withArguments(supertype.name), |
| cls.charOffset, noLength); |
| } else if (!cls.libraryBuilder.mayImplementRestrictedTypes && |
| (denyListedClasses.contains(supertype) || |
| identical(supertype, enumClass) && |
| checkEnumSupertypeIsDenylisted(cls))) { |
| TypeAliasBuilder? aliasBuilder = directSupertypeMap[supertype]; |
| if (aliasBuilder != null) { |
| cls.addProblem( |
| templateExtendingRestricted |
| .withArguments(supertype!.fullNameForErrors), |
| cls.charOffset, |
| noLength, |
| context: [ |
| messageTypedefCause.withLocation( |
| aliasBuilder.fileUri, aliasBuilder.charOffset, noLength), |
| ]); |
| } else { |
| cls.addProblem( |
| templateExtendingRestricted |
| .withArguments(supertype!.fullNameForErrors), |
| cls.charOffset, |
| noLength); |
| } |
| } |
| } |
| |
| // Check that the mixed-in type can be used as a mixin. |
| final TypeBuilder? mixedInTypeBuilder = cls.mixedInTypeBuilder; |
| if (mixedInTypeBuilder != null) { |
| bool isClassBuilder = false; |
| if (mixedInTypeBuilder is NamedTypeBuilder) { |
| TypeDeclarationBuilder? builder = mixedInTypeBuilder.declaration; |
| if (builder is TypeAliasBuilder) { |
| TypeAliasBuilder aliasBuilder = builder; |
| NamedTypeBuilder namedBuilder = mixedInTypeBuilder; |
| builder = aliasBuilder.unaliasDeclaration(namedBuilder.typeArguments, |
| isUsedAsClass: true, |
| usedAsClassCharOffset: namedBuilder.charOffset, |
| usedAsClassFileUri: namedBuilder.fileUri); |
| if (builder is! ClassBuilder) { |
| cls.addProblem( |
| templateIllegalMixin.withArguments(builder!.fullNameForErrors), |
| cls.charOffset, |
| noLength, |
| context: [ |
| messageTypedefCause.withLocation( |
| aliasBuilder.fileUri, aliasBuilder.charOffset, noLength), |
| ]); |
| return; |
| } else if (!cls.libraryBuilder.mayImplementRestrictedTypes && |
| denyListedClasses.contains(builder)) { |
| cls.addProblem( |
| templateExtendingRestricted |
| .withArguments(mixedInTypeBuilder.fullNameForErrors), |
| cls.charOffset, |
| noLength, |
| context: [ |
| messageTypedefUnaliasedTypeCause.withLocation( |
| builder.fileUri, builder.charOffset, noLength), |
| ]); |
| return; |
| } |
| } |
| if (builder is ClassBuilder) { |
| isClassBuilder = true; |
| // Assume that mixin classes fulfill their contract of having no |
| // generative constructors. |
| if (!builder.isMixinClass) { |
| _checkConstructorsForMixin(cls, builder); |
| } |
| } |
| } |
| if (!isClassBuilder) { |
| // TODO(ahe): Either we need to check this for superclass and |
| // interfaces, or this shouldn't be necessary (or handled elsewhere). |
| cls.addProblem( |
| templateIllegalMixin |
| .withArguments(mixedInTypeBuilder.fullNameForErrors), |
| cls.charOffset, |
| noLength); |
| } |
| } |
| } |
| |
| /// Checks that there are no cycles in the class hierarchy, and if so break |
| /// these cycles by removing supertypes. |
| /// |
| /// Returns list of all source classes and extension types in topological |
| /// order. |
| (List<SourceClassBuilder>, List<SourceExtensionTypeDeclarationBuilder>) |
| checkClassCycles(ClassBuilder objectClass) { |
| checkObjectClassHierarchy(objectClass); |
| return handleHierarchyCycles(objectClass); |
| } |
| |
| /// Reports errors for 'base', 'interface', 'final', 'mixin' and 'sealed' |
| /// class modifiers. |
| // TODO(johnniwinther): Merge supertype checking with class hierarchy |
| // computation to better support transitive checking. |
| void _checkSupertypeClassModifiers(SourceClassBuilder cls, |
| Map<ClassBuilder, ClassBuilder> classToBaseOrFinalSuperClass) { |
| bool isClassModifiersEnabled(ClassBuilder typeBuilder) => |
| typeBuilder.libraryBuilder.languageVersion >= |
| ExperimentalFlag.classModifiers.experimentEnabledVersion; |
| |
| bool isSealedClassEnabled(ClassBuilder typeBuilder) => |
| typeBuilder.libraryBuilder.languageVersion >= |
| ExperimentalFlag.sealedClass.experimentEnabledVersion; |
| |
| /// Set when we know whether this library can ignore class modifiers. |
| /// |
| /// The same decision applies to all declarations in the library, |
| /// so the value only needs to be computed once. |
| bool? isExempt; |
| |
| /// Whether the [cls] declaration can ignore (some) class modifiers. |
| /// |
| /// Checks whether the [cls] can ignore modifiers |
| /// from the [supertypeDeclaration]. |
| /// This is only possible if the supertype declaration comes |
| /// from a platform library (`dart:` URI scheme), |
| /// and then only if the library is another platform library which is |
| /// exempt from restrictions on extending otherwise sealed platform types, |
| /// or if the library is a pre-class-modifiers-feature language version |
| /// library. |
| /// |
| /// [checkingBaseOrFinalSubtypeError] indicates that we are checking whether |
| /// to emit a base or final subtype error, see |
| /// [checkForBaseFinalRestriction]. We ignore these in `dart:` libraries no |
| /// matter what, otherwise pre-feature libraries can't use base types with |
| /// modifiers at all. |
| bool mayIgnoreClassModifiers(ClassBuilder supertypeDeclaration, |
| {bool checkingBaseOrFinalSubtypeError = false}) { |
| // Only use this to ignore `final`, `base`, `interface`, and `mixin`. |
| // Nobody can ignore `abstract` or `sealed`. |
| |
| // We already know the library cannot ignore modifiers. |
| if (isExempt == false) return false; |
| |
| // Exception only applies to platform libraries. |
| final LibraryBuilder superLibrary = supertypeDeclaration.libraryBuilder; |
| if (!superLibrary.importUri.isScheme("dart")) return false; |
| |
| // Modifiers in certain libraries like 'dart:ffi' can't be ignored in |
| // pre-feature code. |
| if (superLibrary.importUri.path == 'ffi' && |
| !checkingBaseOrFinalSubtypeError) { |
| return false; |
| } |
| |
| // Remaining tests depend on the source library only, |
| // and the result can be cached. |
| if (isExempt == true) return true; |
| |
| final LibraryBuilder subLibrary = cls.libraryBuilder; |
| |
| // Some platform libraries may implement types like `int`, |
| // even if they are final. |
| if (subLibrary.mayImplementRestrictedTypes) { |
| isExempt = true; |
| return true; |
| } |
| // "Legacy" libraries may ignore `final`, `base` and `interface` |
| // from platform libraries. (But still cannot implement `int`.) |
| if (subLibrary.languageVersion < |
| ExperimentalFlag.classModifiers.experimentEnabledVersion) { |
| isExempt = true; |
| return true; |
| } |
| isExempt = false; |
| return false; |
| } |
| |
| TypeDeclarationBuilder? unaliasDeclaration(TypeBuilder typeBuilder) { |
| TypeDeclarationBuilder? typeDeclarationBuilder = typeBuilder.declaration; |
| if (typeDeclarationBuilder is TypeAliasBuilder) { |
| final TypeAliasBuilder aliasBuilder = typeDeclarationBuilder; |
| final NamedTypeBuilder namedBuilder = typeBuilder as NamedTypeBuilder; |
| typeDeclarationBuilder = aliasBuilder.unaliasDeclaration( |
| namedBuilder.typeArguments, |
| isUsedAsClass: true, |
| usedAsClassCharOffset: namedBuilder.charOffset, |
| usedAsClassFileUri: namedBuilder.fileUri); |
| } |
| return typeDeclarationBuilder; |
| } |
| |
| // All subtypes of a base or final class or mixin must also be base, |
| // final, or sealed. Report an error otherwise. |
| void checkForBaseFinalRestriction(ClassBuilder superclass, |
| {TypeBuilder? implementsBuilder}) { |
| if (classToBaseOrFinalSuperClass.containsKey(cls)) { |
| // We've already visited this class. Don't check it again. |
| return; |
| } else if (cls.isEnum) { |
| // Don't report any errors on enums. They should all be considered |
| // final. |
| return; |
| } |
| |
| final ClassBuilder? cachedBaseOrFinalSuperClass = |
| classToBaseOrFinalSuperClass[superclass]; |
| final bool hasCachedBaseOrFinalSuperClass = |
| cachedBaseOrFinalSuperClass != null; |
| ClassBuilder baseOrFinalSuperClass; |
| if (!superclass.cls.isAnonymousMixin && |
| (superclass.isBase || superclass.isFinal)) { |
| // Prefer the direct base or final superclass |
| baseOrFinalSuperClass = superclass; |
| } else if (hasCachedBaseOrFinalSuperClass) { |
| // There's a base or final class higher up in the class hierarchy. |
| // The superclass is a sealed element or an anonymous class. |
| baseOrFinalSuperClass = cachedBaseOrFinalSuperClass; |
| } else { |
| return; |
| } |
| |
| classToBaseOrFinalSuperClass[cls] = baseOrFinalSuperClass; |
| |
| if (implementsBuilder != null && |
| superclass.isSealed && |
| baseOrFinalSuperClass.libraryBuilder.origin != |
| cls.libraryBuilder.origin) { |
| // This error is reported at the call site. |
| // TODO(johnniwinther): Merge supertype checking with class hierarchy |
| // computation to better support transitive checking. |
| // It's an error to implement a class if it has a supertype from a |
| // different library which is marked base. |
| /*if (baseOrFinalSuperClass.isBase) { |
| cls.addProblem( |
| templateBaseClassImplementedOutsideOfLibrary |
| .withArguments(baseOrFinalSuperClass.fullNameForErrors), |
| implementsBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| context: [ |
| templateBaseClassImplementedOutsideOfLibraryCause |
| .withArguments(superclass.fullNameForErrors, |
| baseOrFinalSuperClass.fullNameForErrors) |
| .withLocation(baseOrFinalSuperClass.fileUri, |
| baseOrFinalSuperClass.charOffset, noLength) |
| ]); |
| }*/ |
| } else if (!cls.isBase && |
| !cls.isFinal && |
| !cls.isSealed && |
| !cls.cls.isAnonymousMixin && |
| !mayIgnoreClassModifiers(baseOrFinalSuperClass, |
| checkingBaseOrFinalSubtypeError: true)) { |
| if (!superclass.isBase && |
| !superclass.isFinal && |
| !superclass.isSealed && |
| !superclass.cls.isAnonymousMixin && |
| superclass.libraryBuilder.languageVersion >= |
| ExperimentalFlag.classModifiers.experimentEnabledVersion) { |
| // Only report an error on the nearest subtype that does not fulfill |
| // the base or final subtype restriction. |
| return; |
| } |
| |
| if (baseOrFinalSuperClass.isFinal) { |
| // Don't check base and final subtyping restriction if the supertype |
| // is a final type used outside of its library. |
| if (cls.libraryBuilder.origin != |
| baseOrFinalSuperClass.libraryBuilder.origin) { |
| // In the special case where the 'baseOrFinalSuperClass' is a core |
| // library class and we are indirectly subtyping from a superclass |
| // that's from a pre-feature library, we want to produce a final |
| // transitivity error. |
| // |
| // For implements clauses with the above scenario, we avoid |
| // over-reporting since there will already be a |
| // [FinalClassImplementedOutsideOfLibrary] error. |
| // |
| // TODO(kallentu): Avoid over-reporting for with clauses. |
| if (baseOrFinalSuperClass.libraryBuilder.origin == |
| superclass.libraryBuilder.origin || |
| !baseOrFinalSuperClass.libraryBuilder.importUri |
| .isScheme("dart") || |
| implementsBuilder != null) { |
| return; |
| } |
| } |
| final Template<Message Function(String, String)> template = |
| cls.isMixinDeclaration |
| ? templateMixinSubtypeOfFinalIsNotBase |
| : templateSubtypeOfFinalIsNotBaseFinalOrSealed; |
| cls.addProblem( |
| template.withArguments(cls.fullNameForErrors, |
| baseOrFinalSuperClass.fullNameForErrors), |
| cls.charOffset, |
| noLength); |
| } else if (baseOrFinalSuperClass.isBase) { |
| final Template<Message Function(String, String)> template = |
| cls.isMixinDeclaration |
| ? templateMixinSubtypeOfBaseIsNotBase |
| : templateSubtypeOfBaseIsNotBaseFinalOrSealed; |
| cls.addProblem( |
| template.withArguments(cls.fullNameForErrors, |
| baseOrFinalSuperClass.fullNameForErrors), |
| cls.charOffset, |
| noLength); |
| } |
| } |
| } |
| |
| final TypeBuilder? supertypeBuilder = cls.supertypeBuilder; |
| if (supertypeBuilder != null) { |
| final TypeDeclarationBuilder? supertypeDeclaration = |
| unaliasDeclaration(supertypeBuilder); |
| if (supertypeDeclaration is ClassBuilder) { |
| checkForBaseFinalRestriction(supertypeDeclaration); |
| |
| if (isClassModifiersEnabled(supertypeDeclaration)) { |
| if (cls.libraryBuilder.origin != |
| supertypeDeclaration.libraryBuilder.origin && |
| !mayIgnoreClassModifiers(supertypeDeclaration)) { |
| if (supertypeDeclaration.isInterface && !cls.isMixinDeclaration) { |
| cls.addProblem( |
| templateInterfaceClassExtendedOutsideOfLibrary |
| .withArguments(supertypeDeclaration.fullNameForErrors), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } else if (supertypeDeclaration.isFinal) { |
| if (cls.isMixinDeclaration) { |
| cls.addProblem( |
| templateFinalClassUsedAsMixinConstraintOutsideOfLibrary |
| .withArguments(supertypeDeclaration.fullNameForErrors), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } else { |
| cls.addProblem( |
| templateFinalClassExtendedOutsideOfLibrary |
| .withArguments(supertypeDeclaration.fullNameForErrors), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } |
| } |
| } |
| } |
| |
| // Report error for extending a sealed class outside of its library. |
| if (isSealedClassEnabled(supertypeDeclaration) && |
| supertypeDeclaration.isSealed && |
| cls.libraryBuilder.origin != |
| supertypeDeclaration.libraryBuilder.origin) { |
| cls.addProblem( |
| templateSealedClassSubtypeOutsideOfLibrary |
| .withArguments(supertypeDeclaration.fullNameForErrors), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } |
| } |
| } |
| |
| final TypeBuilder? mixedInTypeBuilder = cls.mixedInTypeBuilder; |
| if (mixedInTypeBuilder != null) { |
| final TypeDeclarationBuilder? mixedInTypeDeclaration = |
| unaliasDeclaration(mixedInTypeBuilder); |
| if (mixedInTypeDeclaration is ClassBuilder) { |
| checkForBaseFinalRestriction(mixedInTypeDeclaration); |
| |
| if (isClassModifiersEnabled(mixedInTypeDeclaration)) { |
| // Check for classes being used as mixins. Only classes declared with |
| // a 'mixin' modifier are allowed to be mixed in. |
| if (cls.isMixinApplication && |
| !mixedInTypeDeclaration.isMixinDeclaration && |
| !mixedInTypeDeclaration.isMixinClass && |
| !mayIgnoreClassModifiers(mixedInTypeDeclaration)) { |
| cls.addProblem( |
| templateCantUseClassAsMixin |
| .withArguments(mixedInTypeDeclaration.fullNameForErrors), |
| mixedInTypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } |
| } |
| |
| // Report error for mixing in a sealed mixin outside of its library. |
| if (isSealedClassEnabled(mixedInTypeDeclaration) && |
| mixedInTypeDeclaration.isSealed && |
| cls.libraryBuilder.origin != |
| mixedInTypeDeclaration.libraryBuilder.origin) { |
| cls.addProblem( |
| templateSealedClassSubtypeOutsideOfLibrary |
| .withArguments(mixedInTypeDeclaration.fullNameForErrors), |
| mixedInTypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } |
| } |
| } |
| |
| final List<TypeBuilder>? interfaceBuilders = cls.interfaceBuilders; |
| if (interfaceBuilders != null) { |
| for (TypeBuilder interfaceBuilder in interfaceBuilders) { |
| final TypeDeclarationBuilder? interfaceDeclaration = |
| unaliasDeclaration(interfaceBuilder); |
| if (interfaceDeclaration is ClassBuilder) { |
| checkForBaseFinalRestriction(interfaceDeclaration, |
| implementsBuilder: interfaceBuilder); |
| |
| ClassBuilder? checkedClass = interfaceDeclaration; |
| while (checkedClass != null) { |
| if (cls.libraryBuilder.origin != |
| checkedClass.libraryBuilder.origin && |
| !mayIgnoreClassModifiers(checkedClass)) { |
| final List<LocatedMessage> context = [ |
| if (checkedClass != interfaceDeclaration) |
| templateBaseOrFinalClassImplementedOutsideOfLibraryCause |
| .withArguments(interfaceDeclaration.fullNameForErrors, |
| checkedClass.fullNameForErrors) |
| .withLocation(checkedClass.fileUri, |
| checkedClass.charOffset, noLength) |
| ]; |
| |
| if (checkedClass.isBase && !cls.cls.isAnonymousMixin) { |
| // Report an error for a class implementing a base class outside |
| // of its library. |
| final Template<Message Function(String)> template = |
| checkedClass.isMixinDeclaration |
| ? templateBaseMixinImplementedOutsideOfLibrary |
| : templateBaseClassImplementedOutsideOfLibrary; |
| cls.addProblem( |
| template.withArguments(checkedClass.fullNameForErrors), |
| interfaceBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| context: context); |
| // Break to only report one error. |
| break; |
| } else if (checkedClass.isFinal) { |
| // Report an error for a class implementing a final class |
| // outside of its library. |
| final Template<Message Function(String)> template = cls |
| .cls.isAnonymousMixin && |
| checkedClass == interfaceDeclaration |
| ? templateFinalClassUsedAsMixinConstraintOutsideOfLibrary |
| : templateFinalClassImplementedOutsideOfLibrary; |
| cls.addProblem( |
| template.withArguments(checkedClass.fullNameForErrors), |
| interfaceBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| context: context); |
| // Break to only report one error. |
| break; |
| } |
| } |
| checkedClass = classToBaseOrFinalSuperClass[checkedClass]; |
| } |
| |
| // Report error for implementing a sealed class or a sealed mixin |
| // outside of its library. |
| if (isSealedClassEnabled(interfaceDeclaration) && |
| interfaceDeclaration.isSealed && |
| cls.libraryBuilder.origin != |
| interfaceDeclaration.libraryBuilder.origin) { |
| cls.addProblem( |
| templateSealedClassSubtypeOutsideOfLibrary |
| .withArguments(interfaceDeclaration.fullNameForErrors), |
| interfaceBuilder.charOffset ?? TreeNode.noOffset, |
| noLength); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Builds the core AST structure needed for the outline of the component. |
| void buildOutlineNodes() { |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| Library target = library.buildOutlineNodes(coreLibrary); |
| if (library.indexedLibrary != null) { |
| referenceFromIndex ??= new ReferenceFromIndex(); |
| referenceFromIndex!.addIndexedLibrary(target, library.indexedLibrary!); |
| } |
| libraries.add(target); |
| } |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| library.processPendingNullabilities(); |
| } |
| ticker.logMs("Built component"); |
| } |
| |
| Component computeFullComponent() { |
| Set<Library> libraries = new Set<Library>(); |
| List<Library> workList = <Library>[]; |
| for (LibraryBuilder libraryBuilder in loadedLibraryBuilders) { |
| assert( |
| !libraryBuilder.isAugmenting, |
| // Coverage-ignore(suite): Not run. |
| "Unexpected augmentation library $libraryBuilder."); |
| if ((libraryBuilder.loader == this || |
| libraryBuilder.importUri.isScheme("dart") || |
| roots.contains(libraryBuilder.importUri))) { |
| if (libraries.add(libraryBuilder.library)) { |
| workList.add(libraryBuilder.library); |
| } |
| } |
| } |
| while (workList.isNotEmpty) { |
| Library library = workList.removeLast(); |
| for (LibraryDependency dependency in library.dependencies) { |
| if (libraries.add(dependency.targetLibrary)) { |
| workList.add(dependency.targetLibrary); |
| } |
| } |
| } |
| return new Component()..libraries.addAll(libraries); |
| } |
| |
| void computeHierarchy() { |
| if (_hierarchy == null) { |
| hierarchy = new ClassHierarchy(computeFullComponent(), coreTypes, |
| onAmbiguousSupertypes: (Class cls, Supertype a, Supertype b) { |
| // Ignore errors. These have already been reported by the class |
| // hierarchy builder. |
| }); |
| } else { |
| Component component = computeFullComponent(); |
| hierarchy.coreTypes = coreTypes; |
| hierarchy.applyTreeChanges(const [], component.libraries, const [], |
| reissueAmbiguousSupertypesFor: component); |
| } |
| ticker.logMs("Computed class hierarchy"); |
| } |
| |
| /// Creates an [InterfaceType] for the `dart:core` type by the given [name]. |
| /// |
| /// This method can be called before [coreTypes] has been computed and only |
| /// required [coreLibrary] to have been set. |
| InterfaceType createCoreType(String name, Nullability nullability, |
| [List<DartType>? typeArguments]) { |
| assert(_coreLibrary != null, "Core library has not been computed yet."); |
| ClassBuilder classBuilder = |
| coreLibrary.lookupLocalMember(name, required: true) as ClassBuilder; |
| return new InterfaceType(classBuilder.cls, nullability, typeArguments); |
| } |
| |
| void computeCoreTypes(Component component) { |
| assert(_coreTypes == null, "CoreTypes has already been computed"); |
| _coreTypes = new CoreTypes(component); |
| |
| // These types are used on the left-hand side of the is-subtype-of relation |
| // to check if the return types of functions with async, sync*, and async* |
| // bodies are correct. It's valid to use the non-nullable types on the |
| // left-hand side in both opt-in and opt-out code. |
| _futureOfBottom = new InterfaceType(coreTypes.futureClass, |
| Nullability.nonNullable, <DartType>[const NeverType.nonNullable()]); |
| _iterableOfBottom = new InterfaceType(coreTypes.iterableClass, |
| Nullability.nonNullable, <DartType>[const NeverType.nonNullable()]); |
| _streamOfBottom = new InterfaceType(coreTypes.streamClass, |
| Nullability.nonNullable, <DartType>[const NeverType.nonNullable()]); |
| |
| ticker.logMs("Computed core types"); |
| } |
| |
| void checkSupertypes( |
| List<SourceClassBuilder> sourceClasses, |
| List<SourceExtensionTypeDeclarationBuilder> |
| sourceExtensionTypeDeclarations, |
| Class objectClass, |
| Class enumClass, |
| Class underscoreEnumClass) { |
| for (SourceClassBuilder builder in sourceClasses) { |
| assert(builder.libraryBuilder.loader == this && !builder.isAugmenting); |
| builder.checkSupertypes( |
| coreTypes, |
| hierarchyBuilder, |
| objectClass, |
| enumClass, |
| underscoreEnumClass, |
| _macroClassBuilder |
| // Coverage-ignore(suite): Not run. |
| ?.cls); |
| } |
| for (SourceExtensionTypeDeclarationBuilder builder |
| in sourceExtensionTypeDeclarations) { |
| assert(builder.libraryBuilder.loader == this && !builder.isAugmenting); |
| builder.checkSupertypes(coreTypes, hierarchyBuilder); |
| } |
| ticker.logMs("Checked supertypes"); |
| } |
| |
| void checkTypes() { |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| library.checkTypesInOutline(typeInferenceEngine.typeSchemaEnvironment); |
| } |
| ticker.logMs("Checked type arguments of supers against the bounds"); |
| } |
| |
| void checkOverrides(List<SourceClassBuilder> sourceClasses) { |
| List<DelayedCheck> overrideChecks = membersBuilder.takeDelayedChecks(); |
| for (int i = 0; i < overrideChecks.length; i++) { |
| overrideChecks[i].check(membersBuilder); |
| } |
| ticker.logMs("Checked ${overrideChecks.length} overrides"); |
| |
| List<Name> restrictedMemberNames = <Name>[ |
| new Name("index"), |
| new Name("hashCode"), |
| new Name("=="), |
| new Name("values") |
| ]; |
| List<Class?> restrictedMemberDeclarers = <Class?>[ |
| (target.underscoreEnumType.declaration as ClassBuilder).cls, |
| coreTypes.objectClass, |
| coreTypes.objectClass, |
| null |
| ]; |
| for (SourceClassBuilder classBuilder in sourceClasses) { |
| if (classBuilder.isEnum) { |
| for (int i = 0; i < restrictedMemberNames.length; ++i) { |
| Name name = restrictedMemberNames[i]; |
| Class? declarer = restrictedMemberDeclarers[i]; |
| |
| ClassMember? classMember = |
| membersBuilder.getDispatchClassMember(classBuilder.cls, name); |
| if (classMember != null) { |
| Member member = classMember.getMember(membersBuilder); |
| if (member.enclosingClass != declarer && |
| member.enclosingClass != classBuilder.cls && |
| member.isAbstract == false) { |
| classBuilder.libraryBuilder.addProblem( |
| templateEnumInheritsRestricted.withArguments(name.text), |
| classBuilder.charOffset, |
| classBuilder.name.length, |
| classBuilder.fileUri, |
| context: <LocatedMessage>[ |
| messageEnumInheritsRestrictedMember.withLocation( |
| classMember.fileUri, |
| classMember.charOffset, |
| member.name.text.length) |
| ]); |
| } |
| } |
| } |
| } |
| } |
| ticker.logMs("Checked for restricted members inheritance in enums."); |
| |
| typeInferenceEngine.finishTopLevelInitializingFormals(); |
| ticker.logMs("Finished initializing formals"); |
| } |
| |
| void checkAbstractMembers(List<SourceClassBuilder> sourceClasses) { |
| List<ClassMember> delayedMemberChecks = |
| membersBuilder.takeDelayedMemberComputations(); |
| Set<Class> changedClasses = new Set<Class>(); |
| for (int i = 0; i < delayedMemberChecks.length; i++) { |
| delayedMemberChecks[i].getMember(membersBuilder); |
| DeclarationBuilder declarationBuilder = |
| delayedMemberChecks[i].declarationBuilder; |
| switch (declarationBuilder) { |
| case ClassBuilder(): |
| // TODO(johnniwinther): Only invalidate class if a member was added. |
| changedClasses.add(declarationBuilder.cls); |
| case ExtensionTypeDeclarationBuilder(): |
| // TODO(johnniwinther): Should the member be added to the extension |
| // type declaration? |
| break; |
| // Coverage-ignore(suite): Not run. |
| case ExtensionBuilder(): |
| throw new UnsupportedError( |
| "Unexpected declaration ${declarationBuilder}."); |
| } |
| } |
| ticker.logMs( |
| "Computed ${delayedMemberChecks.length} combined member signatures"); |
| |
| hierarchy.applyMemberChanges(changedClasses, findDescendants: false); |
| ticker |
| .logMs("Updated ${changedClasses.length} classes in kernel hierarchy"); |
| } |
| |
| void checkRedirectingFactories( |
| List<SourceClassBuilder> sourceClasses, |
| List<SourceExtensionTypeDeclarationBuilder> |
| sourceExtensionTypeDeclarationBuilders) { |
| // TODO(ahe): Move this to [ClassHierarchyBuilder]. |
| for (SourceClassBuilder builder in sourceClasses) { |
| if (builder.libraryBuilder.loader == this && !builder.isAugmenting) { |
| builder.checkRedirectingFactories( |
| typeInferenceEngine.typeSchemaEnvironment); |
| } |
| } |
| for (SourceExtensionTypeDeclarationBuilder builder |
| in sourceExtensionTypeDeclarationBuilders) { |
| if (builder.libraryBuilder.loader == this && !builder.isAugmenting) { |
| builder.checkRedirectingFactories( |
| typeInferenceEngine.typeSchemaEnvironment); |
| } |
| } |
| ticker.logMs("Checked redirecting factories"); |
| } |
| |
| /// Sets [SourceLibraryBuilder.unpromotablePrivateFieldNames] for any |
| /// libraries in which field promotion is enabled. |
| void computeFieldPromotability() { |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| // TODO(paulberry): what should we do for augmentation libraries? |
| if (library.loader == this && !library.isAugmenting) { |
| library.computeFieldPromotability(); |
| } |
| } |
| ticker.logMs("Computed unpromotable private field names"); |
| } |
| |
| void checkMixins(List<SourceClassBuilder> sourceClasses) { |
| for (SourceClassBuilder builder in sourceClasses) { |
| if (!builder.isAugmenting) { |
| Class? mixedInClass = builder.cls.mixedInClass; |
| if (mixedInClass != null && mixedInClass.isMixinDeclaration) { |
| builder.checkMixinApplication(hierarchy, coreTypes); |
| } |
| } |
| } |
| ticker.logMs("Checked mixin declaration applications"); |
| } |
| |
| /// Checks that super member access from mixin declarations mixed into |
| /// the classes in the [sourceLibraryBuilders] have a concrete target |
| // TODO(johnniwinther): Make this work for when the mixin declaration is from |
| // an outline library. |
| void checkMixinSuperAccesses() { |
| _SuperMemberCache superMemberCache = new _SuperMemberCache(); |
| for (SourceLibraryBuilder libraryBuilder in sourceLibraryBuilders) { |
| Map<SourceClassBuilder, TypeBuilder> mixinApplications = {}; |
| libraryBuilder.takeMixinApplications(mixinApplications); |
| for (MapEntry<SourceClassBuilder, TypeBuilder> entry |
| in mixinApplications.entries) { |
| SourceClassBuilder mixinApplication = entry.key; |
| if (!mixinApplication.isAugmenting) { |
| ClassHierarchyNode node = |
| hierarchyBuilder.getNodeFromClassBuilder(mixinApplication); |
| ClassHierarchyNode? mixedInNode = node.mixedInNode; |
| if (mixedInNode != null) { |
| Class mixedInClass = mixedInNode.classBuilder.cls; |
| List<Supertype> onClause = mixedInClass.onClause; |
| if (onClause.isNotEmpty) { |
| for (Procedure procedure in mixedInClass.procedures) { |
| if (procedure.containsSuperCalls) { |
| procedure.function.body?.accept(new _CheckSuperAccess( |
| libraryBuilder, |
| mixinApplication.cls, |
| entry.value, |
| procedure, |
| superMemberCache)); |
| } |
| } |
| for (Field field in mixedInClass.fields) { |
| if (field.containsSuperCalls) { |
| field.initializer?.accept(new _CheckSuperAccess( |
| libraryBuilder, |
| mixinApplication.cls, |
| entry.value, |
| field, |
| superMemberCache)); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| ticker.logMs("Checked mixin application super-accesses"); |
| } |
| |
| void buildOutlineExpressions(ClassHierarchy classHierarchy, |
| List<DelayedDefaultValueCloner> delayedDefaultValueCloners) { |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| library.buildOutlineExpressions( |
| classHierarchy, delayedDefaultValueCloners); |
| } |
| } |
| |
| void buildClassHierarchy( |
| List<SourceClassBuilder> sourceClasses, |
| List<SourceExtensionTypeDeclarationBuilder> sourceExtensionTypes, |
| ClassBuilder objectClass) { |
| ClassHierarchyBuilder hierarchyBuilder = _hierarchyBuilder = |
| ClassHierarchyBuilder.build( |
| objectClass, sourceClasses, sourceExtensionTypes, this, coreTypes); |
| typeInferenceEngine.hierarchyBuilder = hierarchyBuilder; |
| ticker.logMs("Built class hierarchy"); |
| } |
| |
| void buildClassHierarchyMembers(List<SourceClassBuilder> sourceClasses, |
| List<SourceExtensionTypeDeclarationBuilder> sourceExtensionTypes) { |
| ClassMembersBuilder membersBuilder = _membersBuilder = |
| ClassMembersBuilder.build( |
| hierarchyBuilder, sourceClasses, sourceExtensionTypes); |
| typeInferenceEngine.membersBuilder = membersBuilder; |
| ticker.logMs("Built class hierarchy members"); |
| } |
| |
| void createTypeInferenceEngine() { |
| _typeInferenceEngine = |
| new TypeInferenceEngineImpl(instrumentation, target.benchmarker); |
| } |
| |
| void inferRedirectingFactories(ClassHierarchy classHierarchy, |
| List<DelayedDefaultValueCloner> delayedDefaultValueCloners) { |
| /// Inferring redirecting factories partially overlaps with top-level |
| /// inference, since the formal parameters of the redirection targets should |
| /// be inferred, and they can be formal initializing parameters requiring |
| /// inference. [RedirectingFactoryBuilder.buildOutlineExpressions] can |
| /// invoke inference on those formal parameters. Therefore, the top-level |
| /// inference should be prepared before we can infer redirecting factories. |
| |
| /// 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. |
| typeInferenceEngine.prepareTopLevel(coreTypes, hierarchy); |
| membersBuilder.computeTypes(); |
| |
| // TODO(cstefantsova): Put the redirecting factory inference into a separate |
| // step. |
| |
| // Redirecting factory invocations can occur in outline expressions and |
| // should be processed before them. The outline expressions within |
|