| // 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/experimental_features.dart' | |
| show ExperimentalFeaturesExtension; | |
| 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/abstract_scanner.dart' | |
| show ScannerConfiguration, LanguageVersionChanged; | |
| import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' | |
| show | |
| ErrorToken, | |
| LanguageVersionToken, | |
| Scanner, | |
| ScannerResult, | |
| Token, | |
| scan, | |
| scanDirectives; | |
| import 'package:_fe_analyzer_shared/src/util/libraries_specification.dart' | |
| show Importability; | |
| 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/deprecated_js_interop_libraries.dart'; | |
| import '../api_prototype/experimental_flags.dart'; | |
| import '../api_prototype/file_system.dart'; | |
| import '../base/common.dart'; | |
| import '../base/export.dart' show Export; | |
| import '../base/extension_scope.dart'; | |
| import '../base/import_chains.dart'; | |
| import '../base/loader.dart' show Loader, untranslatableUriScheme; | |
| import '../base/lookup_result.dart'; | |
| import '../base/messages.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/denylisted_classes.dart' | |
| show denylistedCoreClasses, denylistedTypedDataClasses; | |
| import '../codes/diagnostic.dart' as diag; | |
| import '../dill/dill_library_builder.dart'; | |
| import '../kernel/benchmarker.dart' show BenchmarkSubdivides; | |
| import '../kernel/body_builder_context.dart'; | |
| import '../kernel/exhaustiveness.dart'; | |
| import '../kernel/expression_compilation_data.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/internal_ast.dart'; | |
| import '../kernel/kernel_helper.dart' | |
| show DelayedDefaultValueCloner, TypeDependency; | |
| import '../kernel/kernel_target.dart' show KernelTarget; | |
| import '../kernel/resolver.dart'; | |
| import '../kernel/type_builder_computer.dart' show TypeBuilderComputer; | |
| import '../type_inference/type_inference_engine.dart'; | |
| import '../util/expression_evaluation_helpers.dart' | |
| show ExpressionEvaluationHelper; | |
| import '../util/reference_map.dart'; | |
| import 'diet_listener.dart' show DietListener; | |
| import 'diet_parser.dart' show DietParser; | |
| 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_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 implements ProblemReportingHelper { | |
| /// 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; | |
| 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; | |
| } | |
| bool get isClosureContextLoweringEnabled { | |
| return target.backendTarget.flags.isClosureContextLoweringEnabled; | |
| } | |
| int byteCount = 0; | |
| UriOffset? currentUriForCrashReporting; | |
| final List<String> _expectedOutlineFutureProblems = []; | |
| final List<String> _expectedBodyBuildingFutureProblems = []; | |
| new(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(); | |
| } | |
| @override | |
| 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 isPatch = false, | |
| required bool mayImplementRestrictedTypes, | |
| }) { | |
| final bool isDartLib = importUri.isScheme('dart'); | |
| return new SourceCompilationUnitImpl( | |
| importUri: importUri, | |
| fileUri: fileUri, | |
| packageUri: packageUri, | |
| originImportUri: originImportUri, | |
| packageLanguageVersion: packageLanguageVersion, | |
| loader: this, | |
| augmentationRoot: origin, | |
| resolveInLibrary: null, | |
| indexedLibrary: referencesFromIndex, | |
| referenceIsPartOwner: referenceIsPartOwner, | |
| conditionalImportSupported: | |
| origin?.conditionalImportSupported ?? | |
| isDartLib && target.uriTranslator.isLibrarySupported(importUri.path), | |
| importability: | |
| origin?.importability ?? | |
| (isDartLib | |
| ? target.uriTranslator.isLibraryImportable(importUri.path) | |
| : Importability.always), | |
| isAugmenting: origin != null, | |
| 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, | |
| conditionalImportSupported: | |
| compilationUnit?.conditionalImportSupported ?? false, | |
| 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 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 = | |
| diag.languageVersionInvalidInDotPackages; | |
| 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 = diag.languageVersionTooHighPackage | |
| .withArguments( | |
| specifiedMajor: version.major, | |
| specifiedMinor: version.minor, | |
| packageName: packageForLanguageVersion.name, | |
| highestSupportedMajor: target.currentSdkVersion.major, | |
| highestSupportedMinor: target.currentSdkVersion.minor, | |
| ); | |
| packageLanguageVersion = new InvalidLanguageVersion( | |
| fileUri, | |
| 0, | |
| noLength, | |
| target.currentSdkVersion, | |
| false, | |
| ); | |
| } else if (version < target.leastSupportedVersion) { | |
| packageLanguageVersionProblem = diag.languageVersionTooLowPackage | |
| .withArguments( | |
| specifiedMajor: version.major, | |
| specifiedMinor: version.minor, | |
| packageName: packageForLanguageVersion.name, | |
| lowestSupportedMajor: target.leastSupportedVersion.major, | |
| lowestSupportedMinor: 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, | |
| 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 isPatch = false, | |
| }) { | |
| CompilationUnit libraryBuilder = _read( | |
| uri, | |
| fileUri: fileUri, | |
| originImportUri: originImportUri, | |
| origin: origin, | |
| referencesFromIndex: referencesFromIndex, | |
| referenceIsPartOwner: referenceIsPartOwner, | |
| isPatch: isPatch, | |
| addAsRoot: false, | |
| ); | |
| libraryBuilder.recordAccess( | |
| accessor, | |
| charOffset, | |
| noLength, | |
| accessor.fileUri, | |
| ); | |
| if (!_hasLibraryAccess(imported: uri, importer: accessor.importUri) && | |
| !accessor.isAugmenting) { | |
| accessor.addProblem( | |
| diag.platformPrivateLibraryAccess, | |
| charOffset, | |
| noLength, | |
| accessor.fileUri, | |
| ); | |
| } | |
| _issueErrorsOnUnsupportedDartColonImports( | |
| libraryBuilder, | |
| uri, | |
| accessor, | |
| charOffset, | |
| noLength, | |
| ); | |
| return libraryBuilder; | |
| } | |
| void _issueErrorsOnUnsupportedDartColonImports( | |
| CompilationUnit libraryBuilder, | |
| Uri uri, | |
| CompilationUnit accessor, | |
| int charOffset, | |
| int length, | |
| ) { | |
| final Uri importUri = libraryBuilder.importUri; | |
| // Only check for imports of unsupported dart:* libraries. | |
| if (!importUri.isScheme('dart')) { | |
| return; | |
| } | |
| // Untranslatable dart uris are handled in [tokenize]. | |
| if (libraryBuilder.fileUri.isScheme(untranslatableUriScheme)) { | |
| return; | |
| } | |
| // dart:* libraries (and their patch files) are not restricted from | |
| // importing other dart:* libraries. | |
| if (accessor.importUri.isScheme('dart') || | |
| (accessor is SourceCompilationUnit && | |
| accessor.originImportUri.isScheme('dart'))) { | |
| return; | |
| } | |
| final TargetFlags flags = target.backendTarget.flags; | |
| final DartLibrarySupport dartLibrarySupport = | |
| target.backendTarget.dartLibrarySupport; | |
| Message? diagnostic; | |
| final Importability importability = libraryBuilder.importability; | |
| final bool importableWithFlag = | |
| (importability == Importability.withFlag && | |
| // Coverage-ignore(suite): Not run. | |
| flags.includeUnsupportedPlatformLibraryStubs); | |
| if (!dartLibrarySupport.computeDartLibrarySupport( | |
| importUri.path, | |
| isSupportedBySpec: | |
| (importability == Importability.always || importableWithFlag), | |
| )) { | |
| // Web compilers disallow the deprecated JS interop libraries by | |
| // considering them unsupported. Explain how to migrate away from them. | |
| diagnostic = deprecatedJsInteropLibraryNames.contains(importUri.path) | |
| ? diag.deprecatedJsInteropLibraryImport.withArguments(uri: importUri) | |
| : diag.unavailableDartLibrary.withArguments(uri: importUri); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| else if (importableWithFlag) { | |
| // Display a warning for each import of an unsupported library. | |
| diagnostic = diag.unsupportedPlatformDartLibraryImport.withArguments( | |
| uri: importUri, | |
| ); | |
| } | |
| if (diagnostic == null) { | |
| return; | |
| } | |
| accessor.addProblem(diagnostic, charOffset, length, accessor.fileUri); | |
| } | |
| /// 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, | |
| 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( | |
| diag.platformPrivateLibraryAccess, | |
| -1, | |
| noLength, | |
| firstLibrary.importUri, | |
| ); | |
| } else { | |
| addProblem(diag.platformPrivateLibraryAccess, -1, noLength, null); | |
| } | |
| } | |
| if (firstLibrary != null) { | |
| _issueErrorsOnUnsupportedDartColonImports( | |
| libraryBuilder, | |
| uri, | |
| firstLibrary, | |
| -1, | |
| noLength, | |
| ); | |
| } | |
| 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 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, | |
| 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(diag.sourceBodySummary); | |
| } | |
| void logSummary(Template<SummaryTemplate> template) { | |
| ticker.log( | |
| // Coverage-ignore(suite): Not run. | |
| (Duration elapsed, Duration sinceStart) { | |
| int libraryCount = 0; | |
| for (CompilationUnit library in compilationUnits) { | |
| if (library.loader == this) { | |
| libraryCount++; | |
| } | |
| } | |
| double ms = | |
| elapsed.inMicroseconds / Duration.microsecondsPerMillisecond; | |
| Message message = template.withArguments( | |
| count: libraryCount, | |
| bytes: byteCount, | |
| timeMs: ms, | |
| rateBytesPerMs: byteCount / ms, | |
| averageTimeMs: 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( | |
| diag.internalProblemContextSeverity.withArguments( | |
| messageCode: 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 getNativeAnnotation() => target.getNativeAnnotation(this); | |
| void addNativeAnnotation(Annotatable annotatable, String nativeMethodName) { | |
| MemberBuilder constructor = getNativeAnnotation(); | |
| Arguments arguments = new Arguments( | |
| new ExpressionList(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); | |
| } | |
| CoreTypes get coreTypes { | |
| assert(_coreTypes != null, "CoreTypes has not been computed."); | |
| return _coreTypes!; | |
| } | |
| ClassHierarchy get hierarchy => _hierarchy!; | |
| void set hierarchy(ClassHierarchy? value) { | |
| if (_hierarchy != value) { | |
| _hierarchy = value; | |
| _typeEnvironment = null; | |
| } | |
| } | |
| TypeEnvironment get typeEnvironment { | |
| return _typeEnvironment ??= new TypeEnvironment(coreTypes, hierarchy); | |
| } | |
| final InferableTypes inferableTypes = new InferableTypes(); | |
| TypeInferenceEngineImpl get typeInferenceEngine => _typeInferenceEngine!; | |
| ClassHierarchyBuilder get hierarchyBuilder => _hierarchyBuilder!; | |
| ClassMembersBuilder get membersBuilder => _membersBuilder!; | |
| Template<SummaryTemplate> get outlineSummaryTemplate => | |
| diag.sourceOutlineSummary; | |
| /// 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, | |
| bool onlyScanDirectives = false, | |
| }) async { | |
| target.benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.beginSubdivide(BenchmarkSubdivides.tokenize); | |
| Uri fileUri = compilationUnit.fileUri; | |
| // Lookup the file URI in the cache. | |
| Uint8List? bytes = sourceBytes[fileUri]; | |
| if (bytes == null) { | |
| // Error recovery. | |
| if (fileUri.isScheme(untranslatableUriScheme)) { | |
| Uri importUri = compilationUnit.importUri; | |
| if (importUri.isScheme('dart')) { | |
| // We report this error later in [buildOutlines]. | |
| _unavailableDartLibraries.add(compilationUnit); | |
| } else { | |
| compilationUnit.addProblemAtAccessors( | |
| diag.untranslatableUri.withArguments(uri: importUri), | |
| ); | |
| } | |
| bytes = synthesizeSourceForMissingFile(importUri, null); | |
| } else if (!fileUri.hasScheme) { | |
| // Coverage-ignore-block(suite): Not run. | |
| target.benchmarker?.endSubdivide(); | |
| return internalProblem( | |
| diag.internalProblemUriMissingScheme.withArguments(uri: fileUri), | |
| -1, | |
| compilationUnit.importUri, | |
| ); | |
| } else if (fileUri.isScheme(MALFORMED_URI_SCHEME)) { | |
| compilationUnit.addProblemAtAccessors(diag.expectedUri); | |
| 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 = diag.cantReadFile.withArguments( | |
| uri: fileUri, | |
| details: target.context.options.osErrorMessage(e.message), | |
| ); | |
| compilationUnit.addProblemAtAccessors(message); | |
| rawBytes = synthesizeSourceForMissingFile( | |
| compilationUnit.importUri, | |
| message, | |
| ); | |
| } | |
| bytes = rawBytes; | |
| sourceBytes[fileUri] = bytes; | |
| byteCount += rawBytes.length; | |
| } | |
| ScannerConfiguration configuration = new LibraryExperimentalFeatures( | |
| new LibraryFeatures( | |
| target.globalFeatures, | |
| compilationUnit.importUri, | |
| compilationUnit.packageLanguageVersion.version, | |
| ), | |
| ).buildScannerConfiguration(); | |
| LanguageVersionChanged languageVersionChanged = | |
| (Scanner scanner, LanguageVersionToken version) { | |
| if (!suppressLexicalErrors) { | |
| compilationUnit.registerExplicitLanguageVersion( | |
| new Version(version.major, version.minor), | |
| offset: version.offset, | |
| length: version.length, | |
| ); | |
| } | |
| scanner.configuration = new LibraryExperimentalFeatures( | |
| compilationUnit.libraryFeatures, | |
| ).buildScannerConfiguration(); | |
| }; | |
| ScannerResult result; | |
| if (onlyScanDirectives) { | |
| // Coverage-ignore-block(suite): Not run. | |
| result = scanDirectives( | |
| bytes, | |
| configuration: configuration, | |
| languageVersionChanged: languageVersionChanged, | |
| ); | |
| } else { | |
| result = scan( | |
| bytes, | |
| includeComments: includeComments, | |
| configuration: configuration, | |
| languageVersionChanged: languageVersionChanged, | |
| 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({bool onlyDirectives = false}) async { | |
| _ensureCoreLibrary(); | |
| while (_unparsedLibraries.isNotEmpty) { | |
| SourceCompilationUnit compilationUnit = _unparsedLibraries.removeFirst(); | |
| currentUriForCrashReporting = new UriOffset( | |
| compilationUnit.importUri, | |
| TreeNode.noOffset, | |
| ); | |
| await buildOutline(compilationUnit, onlyDirectives: onlyDirectives); | |
| } | |
| 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 = diag.unavailableDartLibrary.withArguments( | |
| uri: 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 = [ | |
| diag.importChainContextSimple | |
| .withArguments( | |
| uri: compilationUnit.importUri, | |
| importChain: importChain | |
| .map((part) => ' $part\n') | |
| .join(), | |
| ) | |
| .withoutLocation(), | |
| ]; | |
| } else { | |
| context = [ | |
| diag.importChainContext | |
| .withArguments( | |
| uri: compilationUnit.importUri, | |
| importChain: importChain | |
| .map((part) => ' $part\n') | |
| .join(), | |
| verboseImportChain: 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 = diag.unavailableDartLibrary.withArguments( | |
| uri: 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, { | |
| bool onlyDirectives = false, | |
| }) async { | |
| Token tokens = await tokenize( | |
| compilationUnit, | |
| onlyScanDirectives: onlyDirectives, | |
| ); | |
| compilationUnit.buildOutline(tokens, onlyDirectives: onlyDirectives); | |
| } | |
| /// Builds all the method bodies found in the given [libraryBuilder]. | |
| Future<Null> buildBody(SourceLibraryBuilder? libraryBuilder) async { | |
| // [library] is only nullable so we can call this a "dummy-time" to get rid | |
| // of a semi-leak. | |
| if (libraryBuilder == 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 = libraryBuilder.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(), | |
| experimentalFeatures: new LibraryExperimentalFeatures( | |
| libraryBuilder.libraryFeatures, | |
| ), | |
| ); | |
| 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(), | |
| experimentalFeatures: new LibraryExperimentalFeatures( | |
| libraryBuilder.libraryFeatures, | |
| ), | |
| ); | |
| parser.parseUnit(tokens); | |
| target.benchmarker?.endSubdivide(); | |
| } | |
| } | |
| DietListener listener = createDietListener( | |
| libraryBuilder: libraryBuilder, | |
| extensionScope: compilationUnit.extensionScope, | |
| compilationUnitScope: compilationUnit.compilationUnitScope, | |
| offsetMap: compilationUnit.offsetMap, | |
| ); | |
| DietParser parser = new DietParser( | |
| listener, | |
| experimentalFeatures: new LibraryExperimentalFeatures( | |
| libraryBuilder.libraryFeatures, | |
| ), | |
| ); | |
| parser.parseUnit(tokens); | |
| for (SourceCompilationUnit compilationUnit in libraryBuilder.parts) { | |
| Token tokens = await tokenize( | |
| compilationUnit, | |
| suppressLexicalErrors: true, | |
| allowLazyStrings: false, | |
| ); | |
| DietListener listener = createDietListener( | |
| libraryBuilder: libraryBuilder, | |
| extensionScope: compilationUnit.extensionScope, | |
| compilationUnitScope: compilationUnit.compilationUnitScope, | |
| offsetMap: compilationUnit.offsetMap, | |
| ); | |
| DietParser parser = new DietParser( | |
| listener, | |
| experimentalFeatures: new LibraryExperimentalFeatures( | |
| libraryBuilder.libraryFeatures, | |
| ), | |
| ); | |
| parser.parseUnit(tokens); | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| Future<Expression> buildExpression( | |
| SourceLibraryBuilder libraryBuilder, | |
| String? enclosingClassOrExtension, | |
| bool isClassInstanceMember, | |
| ExpressionCompilationData expressionCompilationData, | |
| Variable? extensionThis, | |
| ExpressionEvaluationHelper expressionEvaluationHelper, | |
| ) async { | |
| // TODO(johnniwinther): Support expression compilation in a specific | |
| // compilation unit. | |
| ExtensionScope extensionScope = | |
| libraryBuilder.compilationUnit.extensionScope; | |
| 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, | |
| 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, | |
| // 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, | |
| ); | |
| return createResolver().buildSingleExpression( | |
| libraryBuilder: libraryBuilder, | |
| bodyBuilderContext: new ExpressionCompilerProcedureBodyBuildContext( | |
| expressionCompilationData, | |
| libraryBuilder, | |
| declarationBuilder, | |
| isDeclarationInstanceMember: isClassInstanceMember, | |
| expressionEvaluationHelper: expressionEvaluationHelper, | |
| ), | |
| fileUri: libraryBuilder.fileUri, | |
| extensionScope: extensionScope, | |
| scope: memberScope, | |
| token: token, | |
| expressionCompilationData: expressionCompilationData, | |
| expressionEvaluationHelper: expressionEvaluationHelper, | |
| extensionThis: extensionThis, | |
| ); | |
| } | |
| DietListener createDietListener({ | |
| required SourceLibraryBuilder libraryBuilder, | |
| required ExtensionScope extensionScope, | |
| required LookupScope compilationUnitScope, | |
| required OffsetMap offsetMap, | |
| }) { | |
| return new DietListener( | |
| libraryBuilder: libraryBuilder, | |
| extensionScope: extensionScope, | |
| outermostScope: compilationUnitScope, | |
| offsetMap: offsetMap, | |
| ); | |
| } | |
| Resolver createResolver() { | |
| return new Resolver( | |
| classHierarchy: hierarchy, | |
| coreTypes: coreTypes, | |
| typeInferenceEngine: typeInferenceEngine, | |
| benchmarker: target.benchmarker, | |
| ); | |
| } | |
| 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"); | |
| } | |
| void installTypedefTearOffs() { | |
| if (target.backendTarget.isTypedefTearOffLoweringEnabled) { | |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { | |
| library.installTypedefTearOffs(); | |
| } | |
| } | |
| } | |
| 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, extensions and extension types defined in libraries in this | |
| /// [SourceLoader] to [sourceClasses], [sourceExtensions], and | |
| /// [sourceExtensionTypes]. | |
| void collectSourceDeclarations( | |
| List<SourceClassBuilder> sourceClasses, | |
| List<SourceExtensionTypeDeclarationBuilder> sourceExtensionTypes, [ | |
| List<SourceExtensionBuilder>? sourceExtensions, | |
| ]) { | |
| for (SourceLibraryBuilder library in sourceLibraryBuilders) { | |
| library.collectSourceDeclarations( | |
| sourceClasses, | |
| sourceExtensions, | |
| 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 = []; | |
| collectSourceDeclarations(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( | |
| diag.cyclicClassHierarchy.withArguments( | |
| typeName: 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( | |
| diag.illegalMixinDueToConstructors.withArguments( | |
| className: mixinClassBuilder.fullNameForErrors, | |
| ), | |
| classBuilder.fileOffset, | |
| noLength, | |
| classBuilder.fileUri, | |
| context: [ | |
| diag.illegalMixinDueToConstructorsCause | |
| .withArguments(className: 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( | |
| diag.enumSupertypeOfNonAbstractClass.withArguments( | |
| className: 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( | |
| diag.extendingEnum.withArguments(enumName: 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( | |
| diag.extendingRestricted.withArguments( | |
| restrictedName: supertype!.fullNameForErrors, | |
| ), | |
| classBuilder.fileOffset, | |
| noLength, | |
| classBuilder.fileUri, | |
| context: [ | |
| diag.typedefCause.withLocation( | |
| aliasBuilder.fileUri, | |
| aliasBuilder.fileOffset, | |
| noLength, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| classBuilder.libraryBuilder.addProblem( | |
| diag.extendingRestricted.withArguments( | |
| restrictedName: 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( | |
| diag.extendingRestricted.withArguments( | |
| restrictedName: mixedInTypeBuilder.fullNameForErrors, | |
| ), | |
| classBuilder.fileOffset, | |
| noLength, | |
| classBuilder.fileUri, | |
| context: declaration is TypeAliasBuilder | |
| ? [ | |
| diag.typedefUnaliasedTypeCause.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( | |
| diag.illegalMixin.withArguments( | |
| typeName: mixedInTypeBuilder.fullNameForErrors, | |
| ), | |
| classBuilder.fileOffset, | |
| noLength, | |
| classBuilder.fileUri, | |
| context: declaration is TypeAliasBuilder | |
| ? [ | |
| diag.typedefCause.withLocation( | |
| declaration.fileUri, | |
| declaration.fileOffset, | |
| noLength, | |
| ), | |
| ] | |
| : null, | |
| ); | |
| case InvalidBuilder(): | |
| if (!unaliasedDeclaration.errorHasBeenReported) { | |
| // Coverage-ignore-block(suite): Not run. | |
| classBuilder.libraryBuilder.addProblem( | |
| diag.illegalMixin.withArguments( | |
| typeName: mixedInTypeBuilder.fullNameForErrors, | |
| ), | |
| classBuilder.fileOffset, | |
| noLength, | |
| classBuilder.fileUri, | |
| context: declaration is TypeAliasBuilder | |
| ? [ | |
| diag.typedefCause.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({ | |
| required String typeName, | |
| required String supertypeName, | |
| }) | |
| > | |
| template = cls.isMixinDeclaration | |
| ? diag.mixinSubtypeOfFinalIsNotBase | |
| : diag.subtypeOfFinalIsNotBaseFinalOrSealed; | |
| cls.libraryBuilder.addProblem( | |
| template.withArguments( | |
| typeName: cls.fullNameForErrors, | |
| supertypeName: baseOrFinalSuperClass.fullNameForErrors, | |
| ), | |
| cls.fileOffset, | |
| noLength, | |
| cls.fileUri, | |
| ); | |
| } else if (baseOrFinalSuperClass.isBase) { | |
| final Template< | |
| Message Function({ | |
| required String className, | |
| required String superclassName, | |
| }) | |
| > | |
| template = cls.isMixinDeclaration | |
| ? diag.mixinSubtypeOfBaseIsNotBase | |
| : diag.subtypeOfBaseIsNotBaseFinalOrSealed; | |
| cls.libraryBuilder.addProblem( | |
| template.withArguments( | |
| className: cls.fullNameForErrors, | |
| superclassName: 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( | |
| diag.interfaceClassExtendedOutsideOfLibrary.withArguments( | |
| interfaceClassName: 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( | |
| diag.finalClassUsedAsMixinConstraintOutsideOfLibrary | |
| .withArguments( | |
| className: supertypeDeclaration.fullNameForErrors, | |
| ), | |
| supertypeBuilder.charOffset ?? TreeNode.noOffset, | |
| noLength, | |
| supertypeBuilder | |
| .fileUri ?? // Coverage-ignore(suite): Not run. | |
| cls.fileUri, | |
| ); | |
| } else { | |
| cls.libraryBuilder.addProblem( | |
| diag.finalClassExtendedOutsideOfLibrary.withArguments( | |
| className: 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( | |
| diag.sealedClassSubtypeOutsideOfLibrary.withArguments( | |
| sealedClassName: 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( | |
| diag.cantUseClassAsMixin.withArguments( | |
| className: 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( | |
| diag.sealedClassSubtypeOutsideOfLibrary.withArguments( | |
| sealedClassName: 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) | |
| diag.baseOrFinalClassImplementedOutsideOfLibraryCause | |
| .withArguments( | |
| subtypeName: interfaceDeclaration.fullNameForErrors, | |
| causeName: 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({required String typeName})> | |
| template = checkedClass.isMixinDeclaration | |
| ? diag.baseMixinImplementedOutsideOfLibrary | |
| : diag.baseClassImplementedOutsideOfLibrary; | |
| cls.libraryBuilder.addProblem( | |
| template.withArguments( | |
| typeName: 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({required String className})> | |
| template = | |
| cls.cls.isAnonymousMixin && | |
| checkedClass == interfaceDeclaration | |
| ? diag.finalClassUsedAsMixinConstraintOutsideOfLibrary | |
| : diag.finalClassImplementedOutsideOfLibrary; | |
| cls.libraryBuilder.addProblem( | |
| template.withArguments( | |
| className: 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( | |
| diag.sealedClassSubtypeOutsideOfLibrary.withArguments( | |
| sealedClassName: 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; | |
| DartTypeList finalTypeArguments; | |
| if (typeArguments != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| finalTypeArguments = new DartTypeList.from(typeArguments); | |
| } else { | |
| finalTypeArguments = DartTypeList.empty; | |
| } | |
| return new InterfaceType(classBuilder.cls, nullability, finalTypeArguments); | |
| } | |
| 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, | |
| DartTypeList.never1, | |
| ); | |
| _iterableOfBottom = new InterfaceType( | |
| coreTypes.iterableClass, | |
| Nullability.nonNullable, | |
| DartTypeList.never1, | |
| ); | |
| _streamOfBottom = new InterfaceType( | |
| coreTypes.streamClass, | |
| Nullability.nonNullable, | |
| DartTypeList.never1, | |
| ); | |
| 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( | |
| diag.enumInheritsRestricted.withArguments( | |
| memberName: name.text, | |
| ), | |
| classBuilder.fileOffset, | |
| classBuilder.name.length, | |
| classBuilder.fileUri, | |
| context: <LocatedMessage>[ | |
| diag.enumInheritsRestrictedMember.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( | |
| benchmarker: 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( | |
| diag.mainNotFunctionDeclarationExported, | |
| libraryBuilder.fileOffset, | |
| noLength, | |
| libraryBuilder.fileUri, | |
| context: [ | |
| diag.exportedMain.withLocation( | |
| mainBuilder.fileUri!, | |
| mainBuilder.fileOffset, | |
| mainBuilder.name.length, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| libraryBuilder.addProblem( | |
| diag.mainNotFunctionDeclaration, | |
| 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( | |
| diag.mainTooManyRequiredParametersExported, | |
| libraryBuilder.fileOffset, | |
| noLength, | |
| libraryBuilder.fileUri, | |
| context: [ | |
| diag.exportedMain.withLocation( | |
| mainBuilder.fileUri!, | |
| mainBuilder.fileOffset, | |
| mainBuilder.name.length, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| libraryBuilder.addProblem( | |
| diag.mainTooManyRequiredParameters, | |
| mainBuilder.fileOffset, | |
| mainBuilder.name.length, | |
| mainBuilder.fileUri, | |
| ); | |
| } | |
| } else if (procedure.function.namedParameters.any( | |
| (parameter) => parameter.isRequired, | |
| )) { | |
| if (mainBuilder.libraryBuilder != libraryBuilder) { | |
| libraryBuilder.addProblem( | |
| diag.mainRequiredNamedParametersExported, | |
| libraryBuilder.fileOffset, | |
| noLength, | |
| libraryBuilder.fileUri, | |
| context: [ | |
| diag.exportedMain.withLocation( | |
| mainBuilder.fileUri!, | |
| mainBuilder.fileOffset, | |
| mainBuilder.name.length, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| libraryBuilder.addProblem( | |
| diag.mainRequiredNamedParameters, | |
| 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( | |
| diag.mainWrongParameterTypeExported.withArguments( | |
| actualType: parameterType, | |
| expectedType: listOfString, | |
| ), | |
| libraryBuilder.fileOffset, | |
| noLength, | |
| libraryBuilder.fileUri, | |
| context: [ | |
| diag.exportedMain.withLocation( | |
| mainBuilder.fileUri!, | |
| mainBuilder.fileOffset, | |
| mainBuilder.name.length, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| libraryBuilder.addProblem( | |
| diag.mainWrongParameterType.withArguments( | |
| actualType: parameterType, | |
| expectedType: listOfString, | |
| ), | |
| mainBuilder.fileOffset, | |
| mainBuilder.name.length, | |
| mainBuilder.fileUri, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } else if (mainBuilder != null) { | |
| if (mainBuilder.parent != libraryBuilder) { | |
| libraryBuilder.addProblem( | |
| diag.mainNotFunctionDeclarationExported, | |
| libraryBuilder.fileOffset, | |
| noLength, | |
| libraryBuilder.fileUri, | |
| context: [ | |
| diag.exportedMain.withLocation( | |
| mainBuilder.fileUri!, | |
| mainBuilder.fileOffset, | |
| noLength, | |
| ), | |
| ], | |
| ); | |
| } else { | |
| libraryBuilder.addProblem( | |
| diag.mainNotFunctionDeclaration, | |
| mainBuilder.fileOffset, | |
| noLength, | |
| mainBuilder.fileUri, | |
| ); | |
| } | |
| } | |
| } | |
| void checkMainMethods() { | |
| DartType listOfString = new InterfaceType( | |
| coreTypes.listClass, | |
| Nullability.nonNullable, | |
| new DartTypeList(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; | |
| } | |
| @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); | |
| } | |
| } | |
| /// 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) {} | |
| V? operator [](Object key); | |
| bool containsKey(Object key); | |
| } | |
| 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, InternalNode> _externalToInternalMap = {}; | |
| /// Registers that [internalNode] has been emitted as [externalNode] in the | |
| /// generated AST. | |
| void registerExternalNode(InternalNode internalNode, TreeNode externalNode) { | |
| _externalToInternalMap[externalNode] = internalNode; | |
| } | |
| /// Returns the internal node for which [externalNode] was created. | |
| InternalNode? toInternalNode(TreeNode externalNode) { | |
| return _externalToInternalMap[externalNode]; | |
| } | |
| 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 = {}; | |
| new(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 = {}; | |
| new(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; | |
| new( | |
| this._sourceLibraryBuilder, | |
| this._mixinApplicationClass, | |
| this._typeBuilder, | |
| this._enclosingMember, | |
| this.cache, | |
| ); | |
| void _checkMember( | |
| Name name, { | |
| required Template<Message Function({required String memberName})> template, | |
| required bool isSetter, | |
| required int accessFileOffset, | |
| }) { | |
| Member? member = cache.findSuperMember( | |
| _mixinApplicationClass.superclass, | |
| name, | |
| isSetter: isSetter, | |
| ); | |
| if (member == null) { | |
| _sourceLibraryBuilder.addProblem( | |
| template.withArguments(memberName: name.text), | |
| _typeBuilder.charOffset!, | |
| noLength, | |
| _typeBuilder.fileUri!, | |
| context: [ | |
| diag.mixinApplicationNoConcreteMemberContext.withLocation( | |
| _enclosingMember.fileUri, | |
| accessFileOffset, | |
| noLength, | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| @override | |
| void visitSuperMethodInvocation(SuperMethodInvocation node) { | |
| super.visitSuperMethodInvocation(node); | |
| _checkMember( | |
| node.interfaceTarget.name, | |
| isSetter: false, | |
| template: diag.mixinApplicationNoConcreteMethod, | |
| accessFileOffset: node.fileOffset, | |
| ); | |
| } | |
| @override | |
| void visitSuperPropertyGet(SuperPropertyGet node) { | |
| super.visitSuperPropertyGet(node); | |
| _checkMember( | |
| node.interfaceTarget.name, | |
| isSetter: false, | |
| template: diag.mixinApplicationNoConcreteGetter, | |
| accessFileOffset: node.fileOffset, | |
| ); | |
| } | |
| @override | |
| void visitSuperPropertySet(SuperPropertySet node) { | |
| super.visitSuperPropertySet(node); | |
| _checkMember( | |
| node.interfaceTarget.name, | |
| isSetter: true, | |
| template: diag.mixinApplicationNoConcreteSetter, | |
| 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; | |
| } | |
| } |