| // 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. |
| |
| import 'dart:collection' show Queue; |
| import 'dart:convert' show utf8; |
| import 'dart:typed_data' show Uint8List; |
| |
| 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:front_end/src/kernel/internal_ast.dart' |
| show VariableDeclarationImpl; |
| 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/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/lookup_result.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/compilation_unit.dart'; |
| import '../builder/constructor_builder.dart'; |
| import '../builder/declaration_builders.dart'; |
| import '../builder/library_builder.dart'; |
| import '../builder/member_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/type_builder_computer.dart' show TypeBuilderComputer; |
| import '../type_inference/inference_visitor.dart' |
| show ExpressionEvaluationHelper; |
| import '../type_inference/type_inference_engine.dart'; |
| import '../type_inference/type_inferrer.dart'; |
| import '../util/reference_map.dart'; |
| import 'diet_listener.dart' show DietListener; |
| import 'diet_parser.dart' show DietParser, useImplicitCreationExpressionInCfe; |
| import 'offset_map.dart'; |
| import 'outline_builder.dart' show OutlineBuilder; |
| import 'source_class_builder.dart' show SourceClassBuilder; |
| import 'source_compilation_unit.dart' show SourceCompilationUnitImpl; |
| 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 'stack_listener_impl.dart' show offsetForToken; |
| import 'type_parameter_factory.dart'; |
| |
| 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; |
| |
| final ReferenceMap referenceMap = new ReferenceMap(); |
| |
| /// 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; |
| CompilationUnit? _typedDataLibraryCompilationUnit; |
| |
| LibraryBuilder? get typedDataLibrary => |
| _typedDataLibraryCompilationUnit?.libraryBuilder; |
| |
| 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; |
| |
| 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, |
| "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]; |
| |
| 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, "Unexpected part $libraryBuilder."); |
| Uri uri = libraryBuilder.importUri; |
| _markDartLibraries(uri, libraryBuilder.mainCompilationUnit); |
| _compilationUnits[uri] = libraryBuilder.mainCompilationUnit; |
| _loadedLibraryBuilders[uri] = libraryBuilder; |
| } |
| |
| 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 ??= _coreLibraryCompilationUnit!.libraryBuilder; |
| |
| @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. |
| |
| SourceCompilationUnit createSourceCompilationUnit({ |
| required Uri importUri, |
| required Uri fileUri, |
| Uri? packageUri, |
| required Uri originImportUri, |
| required LanguageVersion packageLanguageVersion, |
| SourceCompilationUnit? origin, |
| IndexedLibrary? referencesFromIndex, |
| bool? referenceIsPartOwner, |
| bool isAugmentation = false, |
| bool isPatch = false, |
| required bool mayImplementRestrictedTypes, |
| }) { |
| return new SourceCompilationUnitImpl( |
| importUri: importUri, |
| fileUri: fileUri, |
| packageUri: packageUri, |
| originImportUri: originImportUri, |
| packageLanguageVersion: packageLanguageVersion, |
| loader: this, |
| augmentationRoot: origin, |
| resolveInLibrary: null, |
| indexedLibrary: referencesFromIndex, |
| referenceIsPartOwner: referenceIsPartOwner, |
| isUnsupported: |
| origin?.isUnsupported ?? |
| importUri.isScheme('dart') && |
| !target.uriTranslator.isLibrarySupported(importUri.path), |
| isAugmenting: origin != null, |
| forAugmentationLibrary: isAugmentation, |
| forPatchLibrary: isPatch, |
| mayImplementRestrictedTypes: mayImplementRestrictedTypes, |
| ); |
| } |
| |
| /// 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 SourceCompilationUnit? 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) { |
| packageLanguageVersionProblem = |
| codeLanguageVersionInvalidInDotPackages; |
| packageLanguageVersion = new InvalidLanguageVersion( |
| fileUri, |
| 0, |
| noLength, |
| target.currentSdkVersion, |
| false, |
| ); |
| } else { |
| Version version = new Version( |
| packageForLanguageVersion.languageVersion!.major, |
| packageForLanguageVersion.languageVersion!.minor, |
| ); |
| if (version > target.currentSdkVersion) { |
| packageLanguageVersionProblem = codeLanguageVersionTooHighPackage |
| .withArgumentsOld( |
| version.major, |
| version.minor, |
| packageForLanguageVersion.name, |
| target.currentSdkVersion.major, |
| target.currentSdkVersion.minor, |
| ); |
| packageLanguageVersion = new InvalidLanguageVersion( |
| fileUri, |
| 0, |
| noLength, |
| target.currentSdkVersion, |
| false, |
| ); |
| } else if (version < target.leastSupportedVersion) { |
| packageLanguageVersionProblem = codeLanguageVersionTooLowPackage |
| .withArgumentsOld( |
| version.major, |
| version.minor, |
| packageForLanguageVersion.name, |
| 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; |
| SourceCompilationUnit compilationUnit = createSourceCompilationUnit( |
| importUri: uri, |
| fileUri: fileUri, |
| packageUri: packageUri, |
| originImportUri: originImportUri, |
| packageLanguageVersion: packageLanguageVersion, |
| origin: origin, |
| referencesFromIndex: referencesFromIndex, |
| referenceIsPartOwner: referenceIsPartOwner, |
| isAugmentation: isAugmentation, |
| isPatch: isPatch, |
| mayImplementRestrictedTypes: target.backendTarget.mayDefineRestrictedType( |
| originImportUri, |
| ), |
| ); |
| if (packageLanguageVersionProblem != null) { |
| compilationUnit.addPostponedProblem( |
| packageLanguageVersionProblem, |
| 0, |
| noLength, |
| compilationUnit.fileUri, |
| ); |
| } |
| |
| if (addAsRoot) { |
| roots.add(uri); |
| } |
| |
| _checkForDartCore(uri, compilationUnit); |
| |
| 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(compilationUnit, originImportUri); |
| } |
| _unparsedLibraries.addLast(compilationUnit); |
| |
| return compilationUnit; |
| } |
| |
| DillLibraryBuilder? _lookupDillLibraryBuilder(Uri uri) { |
| DillLibraryBuilder? libraryBuilder = target.dillTarget.loader |
| .lookupLibraryBuilder(uri); |
| if (libraryBuilder != null) { |
| _checkForDartCore(uri, libraryBuilder.mainCompilationUnit); |
| } |
| return libraryBuilder; |
| } |
| |
| void _markDartLibraries(Uri uri, CompilationUnit compilationUnit) { |
| if (uri.isScheme("dart")) { |
| if (uri.path == "core") { |
| _coreLibraryCompilationUnit = compilationUnit; |
| } else if (uri.path == "typed_data") { |
| _typedDataLibraryCompilationUnit = compilationUnit; |
| } |
| } |
| } |
| |
| void _checkForDartCore(Uri uri, CompilationUnit compilationUnit) { |
| _markDartLibraries(uri, 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 (_coreLibraryCompilationUnit == compilationUnit) { |
| 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, |
| SourceCompilationUnit? 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( |
| codePlatformPrivateLibraryAccess, |
| 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( |
| codePlatformPrivateLibraryAccess, |
| -1, |
| noLength, |
| firstLibrary.importUri, |
| ); |
| } else { |
| addProblem(codePlatformPrivateLibraryAccess, -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, |
| SourceCompilationUnit? 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 (_coreLibraryCompilationUnit == 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(_coreLibraryCompilationUnit != null); |
| } |
| } |
| |
| Future<Null> buildBodies(List<SourceLibraryBuilder> libraryBuilders) async { |
| assert(_coreLibraryCompilationUnit != 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(codeSourceBodySummary); |
| } |
| |
| void logSummary(Template<SummaryTemplate, Function> 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.withArgumentsOld( |
| 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, |
| CfeSeverity? 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, |
| CfeSeverity? 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 == CfeSeverity.ignored) return null; |
| String trace = |
| """ |
| message: ${message.problemMessage} |
| charOffset: $charOffset |
| fileUri: $fileUri |
| severity: $severity |
| """; |
| if (!seenMessages.add(trace)) return null; |
| if (message.code.severity == CfeSeverity.error) { |
| _hasSeenError = true; |
| } |
| if (message.code.severity == CfeSeverity.context) { |
| internalProblem( |
| codeInternalProblemContextSeverity.withArgumentsOld(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 == CfeSeverity.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); |
| |
| void addNativeAnnotation(Annotatable annotatable, String nativeMethodName) { |
| MemberBuilder constructor = getNativeAnnotation(); |
| Arguments arguments = new Arguments(<Expression>[ |
| new StringLiteral(nativeMethodName), |
| ]); |
| Expression annotation; |
| if (constructor is ConstructorBuilder) { |
| annotation = new ConstructorInvocation( |
| constructor.invokeTarget as Constructor, |
| arguments, |
| )..isConst = true; |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| annotation = new StaticInvocation( |
| constructor.invokeTarget as Procedure, |
| arguments, |
| )..isConst = true; |
| } |
| |
| annotatable.addAnnotation(annotation); |
| } |
| |
| BodyBuilder createBodyBuilderForOutlineExpression( |
| SourceLibraryBuilder libraryBuilder, |
| BodyBuilderContext bodyBuilderContext, |
| LookupScope scope, |
| Uri fileUri, { |
| LocalScope? formalParameterScope, |
| }) { |
| return new BodyBuilder.forOutlineExpression( |
| libraryBuilder, |
| bodyBuilderContext, |
| scope, |
| fileUri, |
| formalParameterScope: formalParameterScope, |
| ); |
| } |
| |
| 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, Function> get outlineSummaryTemplate => |
| codeSourceOutlineSummary; |
| |
| /// 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, |
| bool allowLazyStrings = true, |
| }) 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( |
| codeUntranslatableUri.withArgumentsOld(importUri), |
| ); |
| } |
| bytes = synthesizeSourceForMissingFile(importUri, null); |
| } else if (!fileUri.hasScheme) { |
| // Coverage-ignore-block(suite): Not run. |
| target.benchmarker?.endSubdivide(); |
| return internalProblem( |
| codeInternalProblemUriMissingScheme.withArgumentsOld(fileUri), |
| -1, |
| compilationUnit.importUri, |
| ); |
| } else if (fileUri.isScheme(MALFORMED_URI_SCHEME)) { |
| compilationUnit.addProblemAtAccessors(codeExpectedUri); |
| bytes = synthesizeSourceForMissingFile(compilationUnit.importUri, null); |
| } |
| if (bytes != null) { |
| sourceBytes[fileUri] = bytes; |
| } |
| } |
| |
| if (bytes == null) { |
| // If it isn't found in the cache, read the file read from the file |
| // system. |
| Uint8List rawBytes; |
| try { |
| rawBytes = await fileSystem.entityForUri(fileUri).readAsBytes(); |
| } on FileSystemException catch (e) { |
| Message message = codeCantReadFile.withArgumentsOld( |
| fileUri, |
| target.context.options.osErrorMessage(e.message), |
| ); |
| compilationUnit.addProblemAtAccessors(message); |
| rawBytes = synthesizeSourceForMissingFile( |
| compilationUnit.importUri, |
| message, |
| ); |
| } |
| bytes = rawBytes; |
| 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, |
| ), |
| 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, |
| ); |
| }, |
| allowLazyStrings: allowLazyStrings, |
| ); |
| Token token = result.tokens; |
| if (!suppressLexicalErrors) { |
| /// 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, |
| bytes, |
| ); |
| } |
| 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:_compact_hash" => defaultDartCompactHashSource, |
| "dart:_internal" => defaultDartInternalSource, |
| "dart:typed_data" => defaultDartTypedDataSource, |
| _ => message == null ? "" : "/* ${message.problemMessage} */", |
| }); |
| } |
| |
| void registerConstructorToBeInferred(InferableMember inferableMember) { |
| _typeInferenceEngine!.toBeInferred[inferableMember.member] = |
| inferableMember; |
| } |
| |
| 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, |
| Uint8List source, |
| ) { |
| sourceBytes[compilationUnit.fileUri] = source; |
| _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 (_unavailableDartLibraries.isNotEmpty) { |
| CompilationUnit? rootLibrary = rootCompilationUnit; |
| LoadedLibraries? loadedLibraries; |
| for (SourceCompilationUnit compilationUnit in _unavailableDartLibraries) { |
| List<LocatedMessage>? context; |
| Uri importUri = compilationUnit.importUri; |
| Message message = codeUnavailableDartLibrary.withArgumentsOld( |
| 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 = [ |
| codeImportChainContextSimple |
| .withArgumentsOld( |
| compilationUnit.importUri, |
| importChain.map((part) => ' $part\n').join(), |
| ) |
| .withoutLocation(), |
| ]; |
| } else { |
| context = [ |
| codeImportChainContext |
| .withArgumentsOld( |
| 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 = codeUnavailableDartLibrary.withArgumentsOld( |
| 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(); |
| } |
| } |
| |
| Future<Null> buildOutline(SourceCompilationUnit compilationUnit) async { |
| Token tokens = await tokenize(compilationUnit); |
| compilationUnit.buildOutline(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; |
| |
| // 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, |
| allowLazyStrings: false, |
| ); |
| |
| 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, |
| enableFeatureEnhancedParts: |
| library.libraryFeatures.enhancedParts.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, |
| enableFeatureEnhancedParts: |
| library.libraryFeatures.enhancedParts.isEnabled, |
| ); |
| parser.parseUnit(tokens); |
| target.benchmarker?.endSubdivide(); |
| } |
| } |
| |
| DietListener listener = createDietListener( |
| library, |
| compilationUnit.compilationUnitScope, |
| compilationUnit.offsetMap, |
| ); |
| DietParser parser = new DietParser( |
| listener, |
| allowPatterns: library.libraryFeatures.patterns.isEnabled, |
| enableFeatureEnhancedParts: |
| library.libraryFeatures.enhancedParts.isEnabled, |
| ); |
| parser.parseUnit(tokens); |
| for (SourceCompilationUnit compilationUnit in library.parts) { |
| Token tokens = await tokenize( |
| compilationUnit, |
| suppressLexicalErrors: true, |
| allowLazyStrings: false, |
| ); |
| DietListener listener = createDietListener( |
| library, |
| compilationUnit.compilationUnitScope, |
| compilationUnit.offsetMap, |
| ); |
| DietParser parser = new DietParser( |
| listener, |
| allowPatterns: library.libraryFeatures.patterns.isEnabled, |
| enableFeatureEnhancedParts: |
| library.libraryFeatures.enhancedParts.isEnabled, |
| ); |
| parser.parseUnit(tokens); |
| } |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| Future<Expression> buildExpression( |
| SourceLibraryBuilder libraryBuilder, |
| String? enclosingClassOrExtension, |
| bool isClassInstanceMember, |
| Procedure procedure, |
| VariableDeclaration? extensionThis, |
| List<VariableDeclarationImpl> extraKnownVariables, |
| ExpressionEvaluationHelper expressionEvaluationHelper, |
| ) async { |
| // TODO(johnniwinther): Support expression compilation in a specific |
| // compilation unit. |
| LookupScope memberScope = |
| libraryBuilder.compilationUnit.compilationUnitScope; |
| |
| DeclarationBuilder? declarationBuilder; |
| if (enclosingClassOrExtension != null) { |
| Builder? builder = memberScope.lookup(enclosingClassOrExtension)?.getable; |
| if (builder is TypeDeclarationBuilder) { |
| switch (builder) { |
| case ClassBuilder(): |
| declarationBuilder = builder; |
| // TODO(johnniwinther): This should be the body scope of the |
| // fragment in which we are compiling the expression. |
| memberScope = new NameSpaceLookupScope( |
| builder.nameSpace, |
| ScopeKind.declaration, |
| parent: TypeParameterScope.fromList( |
| memberScope, |
| builder.typeParameters, |
| ), |
| ); |
| case ExtensionBuilder(): |
| declarationBuilder = builder; |
| // TODO(johnniwinther): This should be the body scope of the |
| // fragment in which we are compiling the expression. |
| memberScope = new NameSpaceLookupScope( |
| builder.nameSpace, |
| ScopeKind.declaration, |
| // TODO(johnniwinther): Shouldn't type parameters be in scope? |
| parent: memberScope, |
| ); |
| case ExtensionTypeDeclarationBuilder(): |
| // TODO(johnniwinther): Handle this case. |
| case TypeAliasBuilder(): |
| case NominalParameterBuilder(): |
| case StructuralParameterBuilder(): |
| case InvalidBuilder(): |
| case BuiltinTypeDeclarationBuilder(): |
| } |
| } |
| } |
| |
| Token token = await tokenize( |
| libraryBuilder.compilationUnit, |
| suppressLexicalErrors: false, |
| allowLazyStrings: false, |
| ); |
| DietListener dietListener = createDietListener( |
| libraryBuilder, |
| memberScope, |
| // 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), |
| ); |
| |
| BodyBuilder listener = dietListener.createListener( |
| new ExpressionCompilerProcedureBodyBuildContext( |
| dietListener, |
| procedure, |
| libraryBuilder, |
| declarationBuilder, |
| isDeclarationInstanceMember: isClassInstanceMember, |
| ), |
| memberScope, |
| thisVariable: extensionThis, |
| ); |
| for (VariableDeclaration variable |
| in procedure.function.positionalParameters) { |
| listener.typeInferrer.assignedVariables.declare(variable); |
| } |
| |
| return listener.parseSingleExpression( |
| new Parser( |
| listener, |
| useImplicitCreationExpression: useImplicitCreationExpressionInCfe, |
| allowPatterns: libraryBuilder.libraryFeatures.patterns.isEnabled, |
| enableFeatureEnhancedParts: |
| libraryBuilder.libraryFeatures.enhancedParts.isEnabled, |
| ), |
| token, |
| procedure.function, |
| extraKnownVariables, |
| expressionEvaluationHelper, |
| ); |
| } |
| |
| DietListener createDietListener( |
| SourceLibraryBuilder library, |
| LookupScope compilationUnitScope, |
| OffsetMap offsetMap, |
| ) { |
| return new DietListener( |
| library, |
| compilationUnitScope, |
| hierarchy, |
| coreTypes, |
| typeInferenceEngine, |
| offsetMap, |
| ); |
| } |
| |
| void resolveParts() { |
| Map<Uri, SourceCompilationUnit> parts = {}; |
| List<SourceLibraryBuilder> sourceLibraries = []; |
| List<SourceCompilationUnit> augmentationCompilationUnits = []; |
| _compilationUnits.forEach((Uri uri, CompilationUnit compilationUnit) { |
| switch (compilationUnit) { |
| case SourceCompilationUnit(): |
| if (compilationUnit.isPart) { |
| parts[uri] = compilationUnit; |
| } else { |
| if (compilationUnit.isAugmenting) { |
| augmentationCompilationUnits.add(compilationUnit); |
| } else { |
| SourceLibraryBuilder sourceLibraryBuilder = compilationUnit |
| .createLibrary(); |
| sourceLibraries.add(sourceLibraryBuilder); |
| _loadedLibraryBuilders[uri] = sourceLibraryBuilder; |
| } |
| } |
| case DillCompilationUnit(): |
| _loadedLibraryBuilders[uri] = compilationUnit.libraryBuilder; |
| } |
| }); |
| |
| Set<Uri> usedParts = new Set<Uri>(); |
| |
| // Include parts in normal libraries. |
| for (SourceLibraryBuilder library in sourceLibraries) { |
| 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 { |
| SourceLibraryBuilder sourceLibraryBuilder = part.createLibrary(); |
| sourceLibraries.add(sourceLibraryBuilder); |
| _loadedLibraryBuilders[uri] = sourceLibraryBuilder; |
| } |
| } |
| ticker.logMs("Resolved parts"); |
| |
| _sourceLibraryBuilders = sourceLibraries; |
| assert( |
| _compilationUnits.values.every( |
| (compilationUnit) => |
| compilationUnit.loader != this || |
| sourceLibraries.contains(compilationUnit.libraryBuilder), |
| ), |
| "Source library not found in sourceLibraryBuilders:" + |
| _compilationUnits.values |
| .where( |
| (compilationUnit) => |
| compilationUnit.loader == this && |
| !sourceLibraries.contains(compilationUnit.libraryBuilder), |
| ) |
| .join(', ') + |
| ".", |
| ); |
| ticker.logMs("Applied augmentations"); |
| } |
| |
| void buildNameSpaces(Iterable<SourceLibraryBuilder> sourceLibraryBuilders) { |
| for (SourceLibraryBuilder sourceLibraryBuilder in sourceLibraryBuilders) { |
| sourceLibraryBuilder.buildNameSpace(); |
| } |
| ticker.logMs("Built name spaces"); |
| } |
| |
| 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 is SourceLibraryBuilder) { |
| library.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) { |
| Iterator<NamedBuilder> iterator = exported.exportNameSpace |
| .filteredIterator(); |
| while (iterator.moveNext()) { |
| NamedBuilder builder = iterator.current; |
| export.addToExportScope(builder.name, builder); |
| } |
| } |
| } |
| bool wasChanged = false; |
| do { |
| wasChanged = false; |
| for (SourceLibraryBuilder exported in both) { |
| for (Export export in exported.exporters) { |
| Iterator<NamedBuilder> iterator = exported.exportNameSpace |
| .filteredIterator(); |
| while (iterator.moveNext()) { |
| NamedBuilder builder = iterator.current; |
| if (export.addToExportScope(builder.name, builder)) { |
| wasChanged = true; |
| } |
| } |
| } |
| } |
| } while (wasChanged); |
| for (LibraryBuilder library in libraryBuilders) { |
| if (library is SourceLibraryBuilder) { |
| library.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"); |
| } |
| |
| 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 finishTypeParameters( |
| Iterable<SourceLibraryBuilder> libraryBuilders, |
| ClassBuilder object, |
| TypeBuilder dynamicType, |
| ) { |
| Map<TypeParameterBuilder, SourceLibraryBuilder> |
| unboundTypeParameterBuilders = {}; |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| library.collectUnboundTypeParameters(unboundTypeParameterBuilders); |
| } |
| |
| // Ensure that type parameters are built after their dependencies by sorting |
| // them topologically using references in bounds. |
| List<TypeParameterBuilder> sortedTypeParameters = |
| sortAllTypeParametersTopologically(unboundTypeParameterBuilders.keys); |
| |
| for (TypeParameterBuilder builder in sortedTypeParameters) { |
| checkTypeParameterDependencies(unboundTypeParameterBuilders[builder]!, [ |
| builder, |
| ]); |
| } |
| |
| for (TypeParameterBuilder builder in sortedTypeParameters) { |
| builder.finish( |
| unboundTypeParameterBuilders[builder]!, |
| object, |
| dynamicType, |
| ); |
| } |
| |
| ticker.logMs( |
| "Resolved ${sortedTypeParameters.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 parameters"); |
| } |
| |
| void computeDefaultTypes( |
| Iterable<SourceLibraryBuilder> libraryBuilders, |
| TypeBuilder dynamicType, |
| TypeBuilder nullType, |
| TypeBuilder bottomType, |
| ClassBuilder objectClass, |
| ) { |
| int count = 0; |
| for (SourceLibraryBuilder library in libraryBuilders) { |
| count += library.computeDefaultTypes( |
| dynamicType, |
| nullType, |
| bottomType, |
| objectClass, |
| ); |
| } |
| ticker.logMs("Computed default types for $count type parameters"); |
| } |
| |
| void finishNativeMethods() { |
| int count = 0; |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| count += library.finishNativeMethods(this); |
| } |
| 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-block(suite): Not run. |
| objectClass.checkObjectSupertypes(); |
| } |
| } |
| |
| /// 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.lookupRequiredLocalMember(denylistedCoreClasses[i]) |
| as ClassBuilder, |
| ); |
| } |
| ClassBuilder enumClass = |
| coreLibrary.lookupRequiredLocalMember("Enum") 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]) |
| ?.getable; |
| 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]; |
| classBuilder.markAsCyclic(objectClass); |
| classes.add(classBuilder); |
| } |
| } |
| |
| _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.libraryBuilder.addProblem( |
| codeCyclicClassHierarchy.withArgumentsOld( |
| extensionTypeBuilder.fullNameForErrors, |
| ), |
| extensionTypeBuilder.fileOffset, |
| noLength, |
| extensionTypeBuilder.fileUri, |
| ); |
| } |
| } |
| |
| ticker.logMs("Checked class hierarchy"); |
| return (classes, extensionsTypes); |
| } |
| |
| void _checkConstructorsForMixin( |
| SourceClassBuilder classBuilder, |
| ClassBuilder mixinClassBuilder, |
| ) { |
| Iterator<ConstructorBuilder> iterator = mixinClassBuilder |
| .filteredConstructorsIterator(includeDuplicates: false); |
| while (iterator.moveNext()) { |
| ConstructorBuilder constructorBuilder = iterator.current; |
| if (!constructorBuilder.isSynthetic) { |
| classBuilder.libraryBuilder.addProblem( |
| codeIllegalMixinDueToConstructors.withArgumentsOld( |
| mixinClassBuilder.fullNameForErrors, |
| ), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| context: [ |
| codeIllegalMixinDueToConstructorsCause |
| .withArgumentsOld(mixinClassBuilder.fullNameForErrors) |
| .withLocation( |
| constructorBuilder.fileUri!, |
| constructorBuilder.fileOffset, |
| noLength, |
| ), |
| ], |
| ); |
| } |
| } |
| } |
| |
| bool checkEnumSupertypeIsDenylisted(SourceClassBuilder classBuilder) { |
| if (!classBuilder.libraryBuilder.libraryFeatures.enhancedEnums.isEnabled) { |
| // Coverage-ignore-block(suite): Not run. |
| classBuilder.libraryBuilder.addProblem( |
| codeEnumSupertypeOfNonAbstractClass.withArgumentsOld(classBuilder.name), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| ); |
| return true; |
| } |
| return false; |
| } |
| |
| void checkClassSupertypes( |
| SourceClassBuilder classBuilder, |
| 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) { |
| classBuilder.libraryBuilder.addProblem( |
| codeExtendingEnum.withArgumentsOld(supertype.name), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| ); |
| } else if (!classBuilder.libraryBuilder.mayImplementRestrictedTypes && |
| (denyListedClasses.contains(supertype) || |
| identical(supertype, enumClass) && |
| checkEnumSupertypeIsDenylisted(classBuilder))) { |
| TypeAliasBuilder? aliasBuilder = directSupertypeMap[supertype]; |
| if (aliasBuilder != null) { |
| classBuilder.libraryBuilder.addProblem( |
| codeExtendingRestricted.withArgumentsOld( |
| supertype!.fullNameForErrors, |
| ), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| context: [ |
| codeTypedefCause.withLocation( |
| aliasBuilder.fileUri, |
| aliasBuilder.fileOffset, |
| noLength, |
| ), |
| ], |
| ); |
| } else { |
| classBuilder.libraryBuilder.addProblem( |
| codeExtendingRestricted.withArgumentsOld( |
| supertype!.fullNameForErrors, |
| ), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| ); |
| } |
| } |
| } |
| |
| // Check that the mixed-in type can be used as a mixin. |
| final TypeBuilder? mixedInTypeBuilder = classBuilder.mixedInTypeBuilder; |
| if (mixedInTypeBuilder != null) { |
| TypeDeclarationBuilder? declaration = mixedInTypeBuilder.declaration; |
| TypeDeclarationBuilder? unaliasedDeclaration = mixedInTypeBuilder |
| .computeUnaliasedDeclaration(isUsedAsClass: true); |
| |
| switch (unaliasedDeclaration) { |
| case ClassBuilder(): |
| if (!classBuilder.libraryBuilder.mayImplementRestrictedTypes && |
| denyListedClasses.contains(unaliasedDeclaration)) { |
| classBuilder.libraryBuilder.addProblem( |
| codeExtendingRestricted.withArgumentsOld( |
| mixedInTypeBuilder.fullNameForErrors, |
| ), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| context: declaration is TypeAliasBuilder |
| ? [ |
| codeTypedefUnaliasedTypeCause.withLocation( |
| unaliasedDeclaration.fileUri, |
| unaliasedDeclaration.fileOffset, |
| noLength, |
| ), |
| ] |
| : null, |
| ); |
| } else { |
| // Assume that mixin classes fulfill their contract of having no |
| // generative constructors. |
| if (!unaliasedDeclaration.isMixinClass) { |
| _checkConstructorsForMixin(classBuilder, unaliasedDeclaration); |
| } |
| } |
| case null: |
| case BuiltinTypeDeclarationBuilder(): |
| case TypeAliasBuilder(): |
| case ExtensionBuilder(): |
| case ExtensionTypeDeclarationBuilder(): |
| case NominalParameterBuilder(): |
| case StructuralParameterBuilder(): |
| // TODO(ahe): Either we need to check this for superclass and |
| // interfaces, or this shouldn't be necessary (or handled elsewhere). |
| classBuilder.libraryBuilder.addProblem( |
| codeIllegalMixin.withArgumentsOld( |
| mixedInTypeBuilder.fullNameForErrors, |
| ), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| context: declaration is TypeAliasBuilder |
| ? [ |
| codeTypedefCause.withLocation( |
| declaration.fileUri, |
| declaration.fileOffset, |
| noLength, |
| ), |
| ] |
| : null, |
| ); |
| case InvalidBuilder(): |
| if (!unaliasedDeclaration.errorHasBeenReported) { |
| // Coverage-ignore-block(suite): Not run. |
| classBuilder.libraryBuilder.addProblem( |
| codeIllegalMixin.withArgumentsOld( |
| mixedInTypeBuilder.fullNameForErrors, |
| ), |
| classBuilder.fileOffset, |
| noLength, |
| classBuilder.fileUri, |
| context: declaration is TypeAliasBuilder |
| ? [ |
| codeTypedefCause.withLocation( |
| declaration.fileUri, |
| declaration.fileOffset, |
| noLength, |
| ), |
| ] |
| : null, |
| ); |
| } |
| } |
| } |
| } |
| |
| /// 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; |
| } |
| |
| // 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 != cls.libraryBuilder) { |
| // 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( |
| codeBaseClassImplementedOutsideOfLibrary |
| .withArguments(baseOrFinalSuperClass.fullNameForErrors), |
| implementsBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| context: [ |
| codeBaseClassImplementedOutsideOfLibraryCause |
| .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 != baseOrFinalSuperClass.libraryBuilder) { |
| // 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 == |
| superclass.libraryBuilder || |
| !baseOrFinalSuperClass.libraryBuilder.importUri.isScheme( |
| "dart", |
| ) || |
| implementsBuilder != null) { |
| return; |
| } |
| } |
| final Template<Message Function(String, String), Function> template = |
| cls.isMixinDeclaration |
| ? codeMixinSubtypeOfFinalIsNotBase |
| : codeSubtypeOfFinalIsNotBaseFinalOrSealed; |
| cls.libraryBuilder.addProblem( |
| template.withArgumentsOld( |
| cls.fullNameForErrors, |
| baseOrFinalSuperClass.fullNameForErrors, |
| ), |
| cls.fileOffset, |
| noLength, |
| cls.fileUri, |
| ); |
| } else if (baseOrFinalSuperClass.isBase) { |
| final Template<Message Function(String, String), Function> template = |
| cls.isMixinDeclaration |
| ? codeMixinSubtypeOfBaseIsNotBase |
| : codeSubtypeOfBaseIsNotBaseFinalOrSealed; |
| cls.libraryBuilder.addProblem( |
| template.withArgumentsOld( |
| cls.fullNameForErrors, |
| baseOrFinalSuperClass.fullNameForErrors, |
| ), |
| cls.fileOffset, |
| noLength, |
| cls.fileUri, |
| ); |
| } |
| } |
| } |
| |
| final TypeBuilder? supertypeBuilder = cls.supertypeBuilder; |
| if (supertypeBuilder != null) { |
| final TypeDeclarationBuilder? supertypeDeclaration = supertypeBuilder |
| .computeUnaliasedDeclaration(isUsedAsClass: true); |
| if (supertypeDeclaration is ClassBuilder) { |
| checkForBaseFinalRestriction(supertypeDeclaration); |
| |
| if (isClassModifiersEnabled(supertypeDeclaration)) { |
| if (cls.libraryBuilder != supertypeDeclaration.libraryBuilder && |
| !mayIgnoreClassModifiers(supertypeDeclaration)) { |
| if (supertypeDeclaration.isInterface && !cls.isMixinDeclaration) { |
| cls.libraryBuilder.addProblem( |
| codeInterfaceClassExtendedOutsideOfLibrary.withArgumentsOld( |
| supertypeDeclaration.fullNameForErrors, |
| ), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| supertypeBuilder.fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } else if (supertypeDeclaration.isFinal) { |
| if (cls.isMixinDeclaration) { |
| cls.libraryBuilder.addProblem( |
| codeFinalClassUsedAsMixinConstraintOutsideOfLibrary |
| .withArgumentsOld(supertypeDeclaration.fullNameForErrors), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| supertypeBuilder |
| .fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } else { |
| cls.libraryBuilder.addProblem( |
| codeFinalClassExtendedOutsideOfLibrary.withArgumentsOld( |
| supertypeDeclaration.fullNameForErrors, |
| ), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| supertypeBuilder |
| .fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } |
| } |
| } |
| } |
| |
| // Report error for extending a sealed class outside of its library. |
| if (isSealedClassEnabled(supertypeDeclaration) && |
| supertypeDeclaration.isSealed && |
| cls.libraryBuilder != supertypeDeclaration.libraryBuilder) { |
| cls.libraryBuilder.addProblem( |
| codeSealedClassSubtypeOutsideOfLibrary.withArgumentsOld( |
| supertypeDeclaration.fullNameForErrors, |
| ), |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| supertypeBuilder.fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } |
| } |
| } |
| |
| final TypeBuilder? mixedInTypeBuilder = cls.mixedInTypeBuilder; |
| if (mixedInTypeBuilder != null) { |
| final TypeDeclarationBuilder? mixedInTypeDeclaration = mixedInTypeBuilder |
| .computeUnaliasedDeclaration(isUsedAsClass: true); |
| 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.libraryBuilder.addProblem( |
| codeCantUseClassAsMixin.withArgumentsOld( |
| mixedInTypeDeclaration.fullNameForErrors, |
| ), |
| mixedInTypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| mixedInTypeBuilder.fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } |
| } |
| |
| // Report error for mixing in a sealed mixin outside of its library. |
| if (isSealedClassEnabled(mixedInTypeDeclaration) && |
| mixedInTypeDeclaration.isSealed && |
| cls.libraryBuilder != mixedInTypeDeclaration.libraryBuilder) { |
| cls.libraryBuilder.addProblem( |
| codeSealedClassSubtypeOutsideOfLibrary.withArgumentsOld( |
| mixedInTypeDeclaration.fullNameForErrors, |
| ), |
| mixedInTypeBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| mixedInTypeBuilder.fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } |
| } |
| } |
| |
| final List<TypeBuilder>? interfaceBuilders = cls.interfaceBuilders; |
| if (interfaceBuilders != null) { |
| for (TypeBuilder interfaceBuilder in interfaceBuilders) { |
| final TypeDeclarationBuilder? interfaceDeclaration = interfaceBuilder |
| .computeUnaliasedDeclaration(isUsedAsClass: true); |
| if (interfaceDeclaration is ClassBuilder) { |
| checkForBaseFinalRestriction( |
| interfaceDeclaration, |
| implementsBuilder: interfaceBuilder, |
| ); |
| |
| ClassBuilder? checkedClass = interfaceDeclaration; |
| while (checkedClass != null) { |
| if (cls.libraryBuilder != checkedClass.libraryBuilder && |
| !mayIgnoreClassModifiers(checkedClass)) { |
| final List<LocatedMessage> context = [ |
| if (checkedClass != interfaceDeclaration) |
| codeBaseOrFinalClassImplementedOutsideOfLibraryCause |
| .withArgumentsOld( |
| interfaceDeclaration.fullNameForErrors, |
| checkedClass.fullNameForErrors, |
| ) |
| .withLocation( |
| checkedClass.fileUri, |
| checkedClass.fileOffset, |
| 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), Function> template = |
| checkedClass.isMixinDeclaration |
| ? codeBaseMixinImplementedOutsideOfLibrary |
| : codeBaseClassImplementedOutsideOfLibrary; |
| cls.libraryBuilder.addProblem( |
| template.withArgumentsOld(checkedClass.fullNameForErrors), |
| interfaceBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| interfaceBuilder |
| .fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| 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), Function> template = |
| cls.cls.isAnonymousMixin && |
| checkedClass == interfaceDeclaration |
| ? codeFinalClassUsedAsMixinConstraintOutsideOfLibrary |
| : codeFinalClassImplementedOutsideOfLibrary; |
| cls.libraryBuilder.addProblem( |
| template.withArgumentsOld(checkedClass.fullNameForErrors), |
| interfaceBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| interfaceBuilder |
| .fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| 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 != interfaceDeclaration.libraryBuilder) { |
| cls.libraryBuilder.addProblem( |
| codeSealedClassSubtypeOutsideOfLibrary.withArgumentsOld( |
| interfaceDeclaration.fullNameForErrors, |
| ), |
| interfaceBuilder.charOffset ?? TreeNode.noOffset, |
| noLength, |
| interfaceBuilder.fileUri ?? // Coverage-ignore(suite): Not run. |
| cls.fileUri, |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Computes the direct super type for all source classes. |
| void computeSupertypes(Iterable<SourceLibraryBuilder> sourceLibraryBuilders) { |
| for (SourceLibraryBuilder libraryBuilder in sourceLibraryBuilders) { |
| libraryBuilder.computeSupertypes(); |
| } |
| } |
| |
| /// 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); |
| } |
| ticker.logMs("Built component"); |
| } |
| |
| Component computeFullComponent() { |
| Set<Library> libraries = new Set<Library>(); |
| List<Library> workList = <Library>[]; |
| for (LibraryBuilder libraryBuilder in loadedLibraryBuilders) { |
| 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( |
| _coreLibraryCompilationUnit != null, |
| "Core library has not been computed yet.", |
| ); |
| ClassBuilder classBuilder = |
| coreLibrary.lookupRequiredLocalMember(name) 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.checkSupertypes( |
| coreTypes, |
| hierarchyBuilder, |
| objectClass, |
| enumClass, |
| underscoreEnumClass, |
| ); |
| } |
| for (SourceExtensionTypeDeclarationBuilder builder |
| in sourceExtensionTypeDeclarations) { |
| assert(builder.libraryBuilder.loader == this); |
| 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( |
| codeEnumInheritsRestricted.withArgumentsOld(name.text), |
| classBuilder.fileOffset, |
| classBuilder.name.length, |
| classBuilder.fileUri, |
| context: <LocatedMessage>[ |
| codeEnumInheritsRestrictedMember.withLocation2( |
| classMember.uriOffset, |
| ), |
| ], |
| ); |
| } |
| } |
| } |
| } |
| } |
| 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.checkRedirectingFactories( |
| typeInferenceEngine.typeSchemaEnvironment, |
| ); |
| } |
| } |
| for (SourceExtensionTypeDeclarationBuilder builder |
| in sourceExtensionTypeDeclarationBuilders) { |
| if (builder.libraryBuilder.loader == this) { |
| 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.computeFieldPromotability(); |
| } |
| } |
| ticker.logMs("Computed unpromotable private field names"); |
| } |
| |
| void checkMixins(List<SourceClassBuilder> sourceClasses) { |
| for (SourceClassBuilder builder in sourceClasses) { |
| 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; |
| 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 prepareTopLevelInference() { |
| /// 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); |
| |
| ticker.logMs("Prepared for top level inference"); |
| } |
| |
| void inferRedirectingFactories( |
| List<DelayedDefaultValueCloner> delayedDefaultValueCloners, |
| ) { |
| assert( |
| typeInferenceEngine.isTypeInferencePrepared, |
| "Top level inference has not been prepared.", |
| ); |
| |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { |
| List<SourceFactoryBuilder>? redirectingFactoryBuilders = |
| library.redirectingFactoryBuilders; |
| if (redirectingFactoryBuilders != null) { |
| for (SourceFactoryBuilder redirectingFactoryBuilder |
| in redirectingFactoryBuilders) { |
| registerConstructorToBeInferred( |
| new InferableRedirectingFactory( |
| redirectingFactoryBuilder, |
| hierarchy, |
| delayedDefaultValueCloners, |
| ), |
| ); |
| } |
| } |
| } |
| |
| ticker.logMs("Performed redirecting factory inference"); |
| } |
| |
| void computeMemberTypes() { |
| assert( |
| typeInferenceEngine.isTypeInferencePrepared, |
| "Top level inference has not been prepared.", |
| ); |
| membersBuilder.computeTypes(); |
| |
| ticker.logMs("Computed member types"); |
| } |
| |
| void performTopLevelInference(List<SourceClassBuilder> sourceClasses) { |
| assert( |
| typeInferenceEngine.isTypeInferencePrepared, |
| "Top level inference has not been prepared.", |
| ); |
| inferableTypes.inferTypes(typeInferenceEngine.hierarchyBuilder); |
| |
| ticker.logMs("Performed top level inference"); |
| } |
| |
| void _checkMainMethods( |
| SourceLibraryBuilder libraryBuilder, |
| DartType listOfString, |
| ) { |
| LookupResult? result = libraryBuilder.exportNameSpace.lookup('main'); |
| Builder? mainBuilder = result?.getable; |
| mainBuilder ??= result?.setable; |
| if (mainBuilder is MemberBuilder) { |
| if (mainBuilder is InvalidBuilder) { |
| // This is an ambiguous export, skip the check. |
| return; |
| } |
| if (mainBuilder.isProperty) { |
| if (mainBuilder.libraryBuilder != libraryBuilder) { |
| libraryBuilder.addProblem( |
| codeMainNotFunctionDeclarationExported, |
| libraryBuilder.fileOffset, |
| noLength, |
| libraryBuilder.fileUri, |
| context: [ |
| codeExportedMain.withLocation( |
| mainBuilder.fileUri!, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| ), |
| ], |
| ); |
| } else { |
| libraryBuilder.addProblem( |
| codeMainNotFunctionDeclaration, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| mainBuilder.fileUri, |
| ); |
| } |
| } else { |
| Procedure procedure = mainBuilder.invokeTarget as Procedure; |
| if (procedure.function.requiredParameterCount > 2) { |
| if (mainBuilder.libraryBuilder != libraryBuilder) { |
| libraryBuilder.addProblem( |
| codeMainTooManyRequiredParametersExported, |
| libraryBuilder.fileOffset, |
| noLength, |
| libraryBuilder.fileUri, |
| context: [ |
| codeExportedMain.withLocation( |
| mainBuilder.fileUri!, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| ), |
| ], |
| ); |
| } else { |
| libraryBuilder.addProblem( |
| codeMainTooManyRequiredParameters, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| mainBuilder.fileUri, |
| ); |
| } |
| } else if (procedure.function.namedParameters.any( |
| (parameter) => parameter.isRequired, |
| )) { |
| if (mainBuilder.libraryBuilder != libraryBuilder) { |
| libraryBuilder.addProblem( |
| codeMainRequiredNamedParametersExported, |
| libraryBuilder.fileOffset, |
| noLength, |
| libraryBuilder.fileUri, |
| context: [ |
| codeExportedMain.withLocation( |
| mainBuilder.fileUri!, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| ), |
| ], |
| ); |
| } else { |
| libraryBuilder.addProblem( |
| codeMainRequiredNamedParameters, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| mainBuilder.fileUri, |
| ); |
| } |
| } else if (procedure.function.positionalParameters.length > 0) { |
| DartType parameterType = |
| procedure.function.positionalParameters.first.type; |
| |
| if (!typeEnvironment.isSubtypeOf(listOfString, parameterType)) { |
| if (mainBuilder.libraryBuilder != libraryBuilder) { |
| libraryBuilder.addProblem( |
| codeMainWrongParameterTypeExported.withArgumentsOld( |
| parameterType, |
| listOfString, |
| ), |
| libraryBuilder.fileOffset, |
| noLength, |
| libraryBuilder.fileUri, |
| context: [ |
| codeExportedMain.withLocation( |
| mainBuilder.fileUri!, |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| ), |
| ], |
| ); |
| } else { |
| libraryBuilder.addProblem( |
| codeMainWrongParameterType.withArgumentsOld( |
| parameterType, |
| listOfString, |
| ), |
| mainBuilder.fileOffset, |
| mainBuilder.name.length, |
| mainBuilder.fileUri, |
| ); |
| } |
| } |
| } |
| } |
| } else if (mainBuilder != null) { |
| if (mainBuilder.parent != libraryBuilder) { |
| libraryBuilder.addProblem( |
| codeMainNotFunctionDeclarationExported, |
| libraryBuilder.fileOffset, |
| noLength, |
| libraryBuilder.fileUri, |
| context: [ |
| codeExportedMain.withLocation( |
| mainBuilder.fileUri!, |
| mainBuilder.fileOffset, |
| noLength, |
| ), |
| ], |
| ); |
| } else { |
| libraryBuilder.addProblem( |
| codeMainNotFunctionDeclaration, |
| mainBuilder.fileOffset, |
| noLength, |
| mainBuilder.fileUri, |
| ); |
| } |
| } |
| } |
| |
| void checkMainMethods() { |
| DartType listOfString = new InterfaceType( |
| coreTypes.listClass, |
| Nullability.nonNullable, |
| [coreTypes.stringNonNullableRawType], |
| ); |
| |
| for (SourceLibraryBuilder libraryBuilder in sourceLibraryBuilders) { |
| _checkMainMethods(libraryBuilder, listOfString); |
| } |
| } |
| |
| void releaseAncillaryResources() { |
| hierarchy = null; |
| _hierarchyBuilder = null; |
| _membersBuilder = null; |
| _typeInferenceEngine = null; |
| _compilationUnits.clear(); |
| libraries.clear(); |
| sourceBytes.clear(); |
| target.releaseAncillaryResources(); |
| _coreTypes = null; |
| instrumentation = null; |
| } |
| |
| @override |
| ClassBuilder computeClassBuilderFromTargetClass(Class cls) { |
| ClassBuilder? classBuilder = referenceMap.lookupClassBuilder(cls.reference); |
| if (classBuilder != null) { |
| return classBuilder; |
| } |
| Library library = cls.enclosingLibrary; |
| LibraryBuilder? libraryBuilder = lookupLoadedLibraryBuilder( |
| library.importUri, |
| ); |
| if (libraryBuilder == null) { |
| return target.dillTarget.loader.computeClassBuilderFromTargetClass(cls); |
| } |
| return libraryBuilder.lookupRequiredLocalMember(cls.name) as ClassBuilder; |
| } |
| |
| @override |
| ExtensionTypeDeclarationBuilder |
| computeExtensionTypeBuilderFromTargetExtensionType( |
| ExtensionTypeDeclaration extensionType, |
| ) { |
| ExtensionTypeDeclarationBuilder? extensionTypeDeclarationBuilder = |
| referenceMap.lookupExtensionTypeDeclarationBuilder( |
| extensionType.reference, |
| ); |
| if (extensionTypeDeclarationBuilder != null) { |
| return extensionTypeDeclarationBuilder; |
| } |
| Library kernelLibrary = extensionType.enclosingLibrary; |
| LibraryBuilder? library = lookupLoadedLibraryBuilder( |
| kernelLibrary.importUri, |
| ); |
| if (library == null) { |
| // Coverage-ignore-block(suite): Not run. |
| return target.dillTarget.loader |
| .computeExtensionTypeBuilderFromTargetExtensionType(extensionType); |
| } |
| return library.lookupRequiredLocalMember(extensionType.name) |
| as ExtensionTypeDeclarationBuilder; |
| } |
| |
| late TypeBuilderComputer _typeBuilderComputer = new TypeBuilderComputer(this); |
| |
| @override |
| TypeBuilder computeTypeBuilder(DartType type) { |
| return _typeBuilderComputer.visit(type); |
| } |
| |
| BodyBuilder createBodyBuilderForField( |
| SourceLibraryBuilder libraryBuilder, |
| BodyBuilderContext bodyBuilderContext, |
| LookupScope enclosingScope, |
| TypeInferrer typeInferrer, |
| Uri uri, |
| ) { |
| return new BodyBuilder.forField( |
| libraryBuilder, |
| bodyBuilderContext, |
| enclosingScope, |
| typeInferrer, |
| uri, |
| ); |
| } |
| } |
| |
| /// A minimal implementation of dart:core that is sufficient to create an |
| /// instance of [CoreTypes] and compile a program. |
| const String defaultDartCoreSource = """ |
| import 'dart:_internal'; |
| import 'dart:async'; |
| |
| export 'dart:async' show Future, Stream; |
| |
| print(object) {} |
| |
| bool identical(a, b) => false; |
| |
| class Iterator<E> { |
| bool moveNext() => null; |
| E get current => null; |
| } |
| |
| class Iterable<E> { |
| Iterator<E> get iterator => null; |
| } |
| |
| class List<E> extends Iterable<E> { |
| factory List.unmodifiable(elements) => null; |
| factory List.empty({bool growable = false}) => null; |
| factory List.filled(int length, E fill, {bool growable = false}) => null; |
| factory List.generate(int length, E generator(int index), |
| {bool growable = true}) => null; |
| factory List.of() => null; |
| void add(E element) {} |
| void addAll(Iterable<E> iterable) {} |
| E operator [](int index) => null; |
| int get length => 0; |
| List<E> sublist(int start, [int? end]) => this; |
| } |
| |
| class _GrowableList<E> implements List<E> { |
| factory _GrowableList(int length) => null; |
| factory _GrowableList.empty() => null; |
| factory _GrowableList.filled() => null; |
| factory _GrowableList.generate(int length, E generator(int index)) => null; |
| factory _GrowableList._literal1(E e0) => null; |
| factory _GrowableList._literal2(E e0, E e1) => null; |
| factory _GrowableList._literal3(E e0, E e1, E e2) => null; |
| factory _GrowableList._literal4(E e0, E e1, E e2, E e3) => null; |
| factory _GrowableList._literal5(E e0, E e1, E e2, E e3, E e4) => null; |
| factory _GrowableList._literal6(E e0, E e1, E e2, E e3, E e4, E e5) => null; |
| factory _GrowableList._literal7(E e0, E e1, E e2, E e3, E e4, E e5, E e6) => null; |
| factory _GrowableList._literal8(E e0, E e1, E e2, E e3, E e4, E e5, E e6, E e7) => null; |
| void add(E element) {} |
| void addAll(Iterable<E> iterable) {} |
| Iterator<E> get iterator => null; |
| E operator [](int index) => null; |
| } |
| |
| class _List<E> { |
| factory _List() => null; |
| factory _List.empty() => null; |
| factory _List.filled() => null; |
| factory _List.generate(int length, E generator(int index)) => null; |
| } |
| |
| class MapEntry<K, V> { |
| K key; |
| V value; |
| } |
| |
| abstract class Map<K, V> extends Iterable { |
| factory Map.unmodifiable(other) => null; |
| factory Map.of(o) = Map<K, V>._of; |
| external factory Map._of(o); |
| Iterable<MapEntry<K, V>> get entries; |
| void operator []=(K key, V value) {} |
| void addAll(Map<K, V> other) {} |
| } |
| |
| abstract class pragma { |
| String name; |
| Object options; |
| } |
| |
| class NoSuchMethodError { |
| factory NoSuchMethodError.withInvocation(receiver, invocation) => throw ''; |
| } |
| |
| class StackTrace {} |
| |
| class Null {} |
| |
| class Object { |
| const Object(); |
| noSuchMethod(invocation) => null; |
| bool operator==(dynamic) {} |
| } |
| |
| abstract class Enum { |
| String get _name; |
| } |
| |
| abstract class _Enum { |
| final int index; |
| final String _name; |
| |
| const _Enum(this.index, this._name); |
| } |
| |
| class String {} |
| |
| class Symbol {} |
| |
| class Set<E> { |
| factory Set() = Set<E>._; |
| external factory Set._(); |
| factory Set.of(o) = Set<E>._of; |
| external factory Set._of(o); |
| bool add(E element) {} |
| void addAll(Iterable<E> iterable) {} |
| } |
| |
| class Type {} |
| |
| class _InvocationMirror { |
| _InvocationMirror._withType(_memberName, _type, _typeArguments, |
| _positionalArguments, _namedArguments); |
| } |
| |
| class bool {} |
| |
| class double extends num {} |
| |
| class int extends num { |
| int operator -() => this; |
| } |
| |
| class num { |
| num operator -() => this; |
| num operator -(num other) => this; |
| bool operator >=(num other) => false; |
| } |
| |
| class Function {} |
| |
| class Record {} |
| |
| class StateError { |
| StateError(String message); |
| } |
| """; |
| |
| /// A minimal implementation of dart:async that is sufficient to create an |
| /// instance of [CoreTypes] and compile program. |
| const String defaultDartAsyncSource = """ |
| void _asyncStarMoveNextHelper(var stream) {} |
| |
| abstract class Completer { |
| factory Completer.sync() => null; |
| |
| get future; |
| |
| complete([value]); |
| |
| completeError(error, [stackTrace]); |
| } |
| |
| class Future<T> { |
| factory Future.microtask(computation) => null; |
| } |
| |
| class FutureOr { |
| } |
| |
| class _Future { |
| void _completeError(Object error, StackTrace stackTrace) {} |
| |
| void _asyncCompleteError(Object error, StackTrace stackTrace) {} |
| } |
| |
| class Stream {} |
| |
| class _StreamIterator { |
| get current => null; |
| |
| moveNext() {} |
| |
| cancel() {} |
| } |
| """; |
| |
| /// A minimal implementation of dart:collection that is sufficient to create an |
| /// instance of [CoreTypes] and compile program. |
| const String defaultDartCollectionSource = """ |
| abstract class LinkedHashMap<K, V> implements Map<K, V> { |
| factory LinkedHashMap( |
| {bool Function(K, K)? equals, |
| int Function(K)? hashCode, |
| bool Function(dynamic)? isValidKey}) => null; |
| } |
| |
| abstract class LinkedHashSet<E> implements Set<E> { |
| factory LinkedHashSet( |
| {bool Function(E, E)? equals, |
| int Function(E)? hashCode, |
| bool Function(dynamic)? isValidKey}) => null; |
| } |
| |
| class _UnmodifiableSet { |
| final Map _map; |
| const _UnmodifiableSet(this._map); |
| } |
| """; |
| |
| /// A minimal implementation of dart:collection that is sufficient to create an |
| /// instance of [CoreTypes] and compile program. |
| const String defaultDartCompactHashSource = """ |
| class _Map<K, V> { |
| } |
| |
| class _Set<E> { |
| } |
| """; |
| |
| /// A minimal implementation of dart:_internal that is sufficient to create an |
| /// instance of [CoreTypes] and compile program. |
| const String defaultDartInternalSource = """ |
| class Symbol { |
| const Symbol(String name); |
| } |
| |
| T unsafeCast<T>(Object v) {} |
| class ReachabilityError { |
| ReachabilityError([message]); |
| } |
| """; |
| |
| /// A minimal implementation of dart:typed_data that is sufficient to create an |
| /// instance of [CoreTypes] and compile program. |
| const String defaultDartTypedDataSource = """ |
| class Endian { |
| static const Endian little = null; |
| static const Endian big = null; |
| static final Endian host = null; |
| } |
| """; |
| |
| // Coverage-ignore(suite): Not run. |
| class SourceLoaderDataForTesting { |
| final Map<TreeNode, TreeNode> _aliasMap = {}; |
| |
| /// Registers that [original] has been replaced by [alias] in the generated |
| /// AST. |
| void registerAlias(TreeNode original, TreeNode alias) { |
| _aliasMap[alias] = original; |
| } |
| |
| /// Returns the original node for [alias] or [alias] if it was not registered |
| /// as an alias. |
| TreeNode toOriginal(TreeNode alias) { |
| return _aliasMap[alias] ?? alias; |
| } |
| |
| final ExhaustivenessDataForTesting exhaustivenessData = |
| new ExhaustivenessDataForTesting(); |
| } |
| |
| class _SourceClassGraph implements Graph<SourceClassBuilder> { |
| @override |
| final List<SourceClassBuilder> vertices; |
| final ClassBuilder _objectClass; |
| final Map<SourceClassBuilder, Map<TypeDeclarationBuilder?, TypeAliasBuilder?>> |
| directSupertypeMap = {}; |
| final Map<SourceClassBuilder, List<SourceClassBuilder>> _supertypeMap = {}; |
| |
| _SourceClassGraph(this.vertices, this._objectClass); |
| |
| List<SourceClassBuilder> computeSuperClasses(SourceClassBuilder cls) { |
| Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypes = |
| directSupertypeMap[cls] = cls.computeDirectSupertypes(_objectClass); |
| List<SourceClassBuilder> superClasses = []; |
| for (TypeDeclarationBuilder? directSupertype in directSupertypes.keys) { |
| if (directSupertype is SourceClassBuilder) { |
| superClasses.add(directSupertype); |
| } |
| } |
| return superClasses; |
| } |
| |
| @override |
| Iterable<SourceClassBuilder> neighborsOf(SourceClassBuilder vertex) { |
| return _supertypeMap[vertex] ??= computeSuperClasses(vertex); |
| } |
| } |
| |
| class _SourceExtensionTypeGraph |
| implements Graph<SourceExtensionTypeDeclarationBuilder> { |
| @override |
| final List<SourceExtensionTypeDeclarationBuilder> vertices; |
| final Map< |
| SourceExtensionTypeDeclarationBuilder, |
| Map<TypeDeclarationBuilder?, TypeAliasBuilder?> |
| > |
| directSupertypeMap = {}; |
| final Map< |
| SourceExtensionTypeDeclarationBuilder, |
| List<SourceExtensionTypeDeclarationBuilder> |
| > |
| _supertypeMap = {}; |
| |
| _SourceExtensionTypeGraph(this.vertices); |
| |
| List<SourceExtensionTypeDeclarationBuilder> computeSuperClasses( |
| SourceExtensionTypeDeclarationBuilder extensionTypeBuilder, |
| ) { |
| Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypes = |
| directSupertypeMap[extensionTypeBuilder] = extensionTypeBuilder |
| .computeDirectSupertypes(); |
| List<SourceExtensionTypeDeclarationBuilder> superClasses = []; |
| for (TypeDeclarationBuilder? directSupertype in directSupertypes.keys) { |
| if (directSupertype is SourceExtensionTypeDeclarationBuilder) { |
| superClasses.add(directSupertype); |
| } |
| } |
| return superClasses; |
| } |
| |
| @override |
| Iterable<SourceExtensionTypeDeclarationBuilder> neighborsOf( |
| SourceExtensionTypeDeclarationBuilder vertex, |
| ) { |
| return _supertypeMap[vertex] ??= computeSuperClasses(vertex); |
| } |
| } |
| |
| /// Visitor that checks that super accesses have a concrete target. |
| // TODO(johnniwinther): Update this to perform member cloning when needed by |
| // the backend. |
| class _CheckSuperAccess extends RecursiveVisitor { |
| final SourceLibraryBuilder _sourceLibraryBuilder; |
| final Class _mixinApplicationClass; |
| final TypeBuilder _typeBuilder; |
| final Member _enclosingMember; |
| final _SuperMemberCache cache; |
| |
| _CheckSuperAccess( |
| this._sourceLibraryBuilder, |
| this._mixinApplicationClass, |
| this._typeBuilder, |
| this._enclosingMember, |
| this.cache, |
| ); |
| |
| void _checkMember( |
| Name name, { |
| required Template<Message Function(String name), Function> template, |
| required bool isSetter, |
| required int accessFileOffset, |
| }) { |
| Member? member = cache.findSuperMember( |
| _mixinApplicationClass.superclass, |
| name, |
| isSetter: isSetter, |
| ); |
| if (member == null) { |
| _sourceLibraryBuilder.addProblem( |
| template.withArgumentsOld(name.text), |
| _typeBuilder.charOffset!, |
| noLength, |
| _typeBuilder.fileUri!, |
| context: [ |
| codeMixinApplicationNoConcreteMemberContext.withLocation( |
| _enclosingMember.fileUri, |
| accessFileOffset, |
| noLength, |
| ), |
| ], |
| ); |
| } |
| } |
| |
| @override |
| void visitSuperMethodInvocation(SuperMethodInvocation node) { |
| super.visitSuperMethodInvocation(node); |
| _checkMember( |
| node.interfaceTarget.name, |
| isSetter: false, |
| template: codeMixinApplicationNoConcreteMethod, |
| accessFileOffset: node.fileOffset, |
| ); |
| } |
| |
| @override |
| void visitSuperPropertyGet(SuperPropertyGet node) { |
| super.visitSuperPropertyGet(node); |
| _checkMember( |
| node.interfaceTarget.name, |
| isSetter: false, |
| template: codeMixinApplicationNoConcreteGetter, |
| accessFileOffset: node.fileOffset, |
| ); |
| } |
| |
| @override |
| void visitSuperPropertySet(SuperPropertySet node) { |
| super.visitSuperPropertySet(node); |
| _checkMember( |
| node.interfaceTarget.name, |
| isSetter: true, |
| template: codeMixinApplicationNoConcreteSetter, |
| accessFileOffset: node.fileOffset, |
| ); |
| } |
| } |
| |
| /// Cache of concrete members, used by [_CheckSuperAccess] to check that super |
| /// accesses have a concrete target. |
| class _SuperMemberCache { |
| Map<Class, Map<Name, Member>> _getterMaps = {}; |
| Map<Class, Map<Name, Member>> _setterMaps = {}; |
| |
| Map<Name, Member> _computeGetters(Class cls) { |
| Map<Name, Member> cache = {}; |
| for (Procedure procedure in cls.procedures) { |
| if (procedure.kind != ProcedureKind.Setter && !procedure.isAbstract) { |
| cache[procedure.name] = procedure; |
| } |
| } |
| for (Field field in cls.fields) { |
| cache[field.name] = field; |
| } |
| return cache; |
| } |
| |
| Map<Name, Member> _computeSetters(Class cls) { |
| Map<Name, Member> cache = {}; |
| for (Procedure procedure in cls.procedures) { |
| if (procedure.kind == ProcedureKind.Setter && !procedure.isAbstract) { |
| cache[procedure.name] = procedure; |
| } |
| } |
| for (Field field in cls.fields) { |
| if (field.hasSetter) { |
| cache[field.name] = field; |
| } |
| } |
| return cache; |
| } |
| |
| Map<Name, Member> _getConcreteMembers(Class cls, {required bool isSetter}) { |
| if (isSetter) { |
| return _setterMaps[cls] ??= _computeSetters(cls); |
| } else { |
| return _getterMaps[cls] ??= _computeGetters(cls); |
| } |
| } |
| |
| Member? findSuperMember( |
| Class? superClass, |
| Name name, { |
| required bool isSetter, |
| }) { |
| while (superClass != null) { |
| Map<Name, Member> cache = _getConcreteMembers( |
| superClass, |
| isSetter: isSetter, |
| ); |
| Member? member = cache[name]; |
| if (member != null) { |
| return member; |
| } |
| superClass = superClass.superclass; |
| } |
| return null; |
| } |
| } |
| |
| /// This enum is used to mark the expected compilation phase for a compile-time |
| /// error to be reported. |
| enum CompilationPhaseForProblemReporting { |
| /// The outline building phase. |
| /// |
| /// The outline building phase includes outline expressions, such as default |
| /// values of parameters, annotations, and initializers of top-level constant |
| /// fields. |
| outline, |
| |
| /// The body building phase. |
| /// |
| /// The body building phase includes initializers of non-constant fields, |
| /// bodies of method, getters, setters, constructors, etc. |
| bodyBuilding, |
| } |