blob: 5c12393b50ce41b5422ebb3dea60632c3e3ac8ef [file]
// Copyright (c) 2020, 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 'package:_fe_analyzer_shared/src/scanner/string_canonicalizer.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/scope.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/fine/requirements.dart';
import 'package:analyzer/src/summary2/combinator.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
/// The scope for the initializers in a constructor.
class ConstructorInitializerScope extends EnclosedScope {
ConstructorInitializerScope(
super.parent,
ConstructorElement element,
List<FormalParameterFragmentImpl> formalParameterFragments,
) {
var hasWildcardVariables = element.library.featureSet.isEnabled(
Feature.wildcard_variables,
);
for (var formalParameter in formalParameterFragments) {
if (formalParameter.isOriginOtherFragmentOfEnclosing) {
continue;
}
var name = formalParameter.name;
if (name == null || name == '_' && hasWildcardVariables) {
continue;
}
// If the parameter is a private named parameter, then the formal
// parameter's name is the public name, but in the constructor initializer
// list, it is referred to by its private name.
if (formalParameter case FieldFormalParameterFragmentImpl(
:var privateName?,
)) {
_getters[privateName] ??= formalParameter.element;
} else {
_getters[name] ??= formalParameter.element;
}
}
}
}
/// The scope of the names provided by the `@docImport`s of a file.
///
/// Doc imports are scoped like imports with the parts-with-imports feature:
/// the scope of a file looks up its doc import prefixes, then the names that
/// it doc-imports without a prefix, and then the scope of the enclosing file.
/// A part file can shadow the names doc-imported by its enclosing files, and
/// extend their prefixes.
class DocImportScope with _FileImportsScope implements Scope {
@override
final LibraryFragmentImpl _libraryFragment;
@override
final DocImportScope? _enclosingScope;
final List<LibraryImportImpl> _imports;
@override
final Map<String, DocImportPrefixElementImpl> _prefixElements = {};
/// The names imported without a prefix.
///
/// Built lazily, because most documentation comments reference only names
/// that are found in their lexical scope. It is also built when a doc import
/// prefix of a part file searches past this file for a prefix to extend.
@override
late final PrefixScope _noPrefixScope = PrefixScope(
libraryFragment: _libraryFragment,
parent: null,
parentSearchedScopes: const [],
libraryImports: _imports,
prefix: null,
);
DocImportScope({
required LibraryFragmentImpl libraryFragment,
required DocImportScope? parent,
required List<LibraryImportImpl> imports,
}) : _libraryFragment = libraryFragment,
_enclosingScope = parent,
_imports = imports {
var prefixes = <DocImportPrefixElementImpl>{};
for (var import in imports) {
var prefix = import.prefix?.element;
if (prefix is DocImportPrefixElementImpl) {
prefixes.add(prefix);
if (prefix.name case var name?) {
_prefixElements[name] = prefix;
}
}
}
_buildPrefixScopes(imports: imports, prefixes: prefixes);
}
@override
ScopeLookupResult lookup(String id) {
if (_libraryFragment.isAllowedAsPrefixName(id)) {
if (_prefixElements[id] case var prefixElement?) {
return ScopeLookupResultImpl(getter: prefixElement, setter: null);
}
}
var result = _noPrefixScope.lookup(id);
if (result.getter != null || result.setter != null) {
return result;
}
if (_enclosingScope case var enclosingScope?) {
return enclosingScope.lookup(id);
}
return result;
}
}
/// The scope that looks up elements in documentation comments.
///
/// Elements of the lexical scope shadow the names from doc imports.
class DocumentationCommentScope implements Scope {
final Scope _innerScope;
final DocImportScope _docImportScope;
DocumentationCommentScope(this._innerScope, this._docImportScope);
@override
ScopeLookupResult lookup(String id) {
var result = _innerScope.lookup(id);
if (result.getter != null || result.setter != null) return result;
return _docImportScope.lookup(id);
}
}
/// A scope that is lexically enclosed in another scope.
class EnclosedScope with _GettersAndSetters implements Scope {
final Scope _parent;
EnclosedScope(Scope parent) : _parent = parent;
Scope get parent => _parent;
@override
ScopeLookupResult lookup(String id) {
var getter = _getters[id];
var setter = _setters[id];
if (getter != null || setter != null) {
return ScopeLookupResultImpl(getter: getter, setter: setter);
}
return _parent.lookup(id);
}
}
/// The scope defined by an extension.
class ExtensionScope extends EnclosedScope {
ExtensionScope(super.parent, ExtensionElement element) {
element.getters.forEach(_addGetter);
element.setters.forEach(_addSetter);
element.methods.forEach(_addGetter);
}
}
class FormalParameterScope extends EnclosedScope {
FormalParameterScope(
super.parent,
List<FormalParameterFragmentImpl> fragments, {
required FeatureSet featureSet,
}) {
for (var fragment in fragments) {
if (fragment.isOriginOtherFragmentOfEnclosing ||
fragment is FieldFormalParameterFragmentImpl ||
fragment is SuperFormalParameterFragmentImpl) {
continue;
}
var name = fragment.name;
if (featureSet.isEnabled(Feature.wildcard_variables) && name == '_') {
continue;
}
if (name != null) {
_getters[name] ??= fragment.element;
}
}
}
}
/// Tracking information for all import in [LibraryFragmentImpl].
class ImportsTracking {
/// Tracking information for each import prefix.
final Map<PrefixElementImpl?, ImportsTrackingOfPrefix> map;
ImportsTracking({required this.map});
/// The elements that are used from [import].
Set<Element> elementsOf(LibraryImportImpl import) {
return trackerOf(import)?.importToUsedElements[import] ?? {};
}
/// Records that [element] is used, and returns whether any import of
/// this fragment provides it.
bool notifyExtensionUsed(ExtensionElement element) {
var isProvided = false;
for (var tracking in map.values) {
isProvided |= tracking.notifyExtensionUsed(element);
}
return isProvided;
}
ImportsTrackingOfPrefix? trackerOf(LibraryImportImpl import) {
var prefix = import.prefix?.element;
return map[prefix];
}
}
class ImportsTrackingOfPrefix {
final PrefixScope scope;
/// Key: an element.
/// Value: the imports that provide the element.
final Map<Element, List<LibraryImportImpl>> _elementImports = {};
/// Key: an import.
/// Value: used elements imported from the import.
final Map<LibraryImportImpl, Set<Element>> importToUsedElements = {};
/// Key: an import.
/// Value: used elements imported from the import.
/// Excludes elements from deprecated exports.
final Map<LibraryImportImpl, Set<Element>> importToAccessedElements2 = {};
/// Whether the prefix is used without naming an element, so no lookup
/// tells which of the imports with this prefix is used.
///
/// This happens in comment references like `[math]`, where it is allowed,
/// and in incomplete code like `math.`, where the parser reports the
/// missing name. This makes the whole group of imports "used".
bool hasPrefixUsedWithoutName = false;
/// We set it temporarily to `false` while resolving combinators.
bool active = true;
ImportsTrackingOfPrefix({required this.scope}) {
_buildElementToImportsMap();
}
/// The elements that are used from [import].
Set<Element> elementsOf(LibraryImportImpl import) {
return importToUsedElements[import] ?? {};
}
/// The subset of [elementsOf], excludes elements that are from deprecated
/// exports inside the imported library.
Set<Element> elementsOf2(LibraryImportImpl import) {
var result = importToAccessedElements2[import];
if (result != null) {
return result;
}
var accessedElements = elementsOf(import);
// SAFETY: the scope adds only imports with libraries.
var importedLibrary = import.importedLibrary!;
var elementFactory = importedLibrary.internal.elementFactory;
for (var entry in importedLibrary.exportEntries) {
var element = elementFactory.elementOfReference3(entry.reference);
// Check only accessed elements.
if (!accessedElements.contains(element)) {
continue;
}
// We want to exclude only deprecated exports.
if (!importedLibrary.isFromDeprecatedExport(entry)) {
continue;
}
// OK, we have to clone the set, and remove the element.
result ??= accessedElements.toSet();
result.remove(element);
}
result ??= accessedElements;
return importToAccessedElements2[import] = result;
}
void lookupResult(Element? element) {
if (!active) {
return;
}
if (element == null) {
return;
}
if (element is MultiplyDefinedElement) {
return;
}
// SAFETY: if we have `element`, it is from a local import.
var imports = _elementImports[element]!;
for (var import in imports) {
(importToUsedElements[import] ??= {}).add(element);
}
}
/// Records that [element] is used, and returns whether any import of
/// this scope provides it.
bool notifyExtensionUsed(ExtensionElement element) {
var imports = _elementImports[element];
if (imports == null) {
return false;
}
for (var import in imports) {
(importToUsedElements[import] ??= {}).add(element);
}
return true;
}
void notifyPrefixUsedWithoutName() {
hasPrefixUsedWithoutName = true;
}
void _buildElementToImportsMap() {
for (var import in scope._importElements) {
var importedLibrary = import.importedLibrary!;
var elementFactory = importedLibrary.internal.elementFactory;
var combinators = import.combinators.build();
for (var entry in importedLibrary.exportEntries) {
if (combinators.allows(entry.name)) {
var element = elementFactory.elementOfReference3(entry.reference);
(_elementImports[element] ??= []).add(import);
}
}
}
}
}
/// The scope defined by an instance element.
class InstanceScope extends EnclosedScope {
InstanceScope(super.parent, InstanceElement element) {
element.getters.forEach(_addGetter);
element.setters.forEach(_addSetter);
element.methods.forEach(_addGetter);
}
@override
ScopeLookupResult lookup(String id) {
// When a lexical lookup encounters an instance member with the requested
// basename, the returned result should have null as the getter and setter
// (and that is the result, so we should not search the enclosing scope).
// This corresponds to the following rule from the specification:
// "Consider the case where D is an instance member declaration in a class
// or mixin A. The lexical lookup then yields nothing."
var getter = _getters[id];
var setter = _setters[id];
if (getter == null && setter == null) {
return _parent.lookup(id);
}
if (_isStatic(getter) || _isStatic(setter)) {
return ScopeLookupResultImpl(getter: getter, setter: setter);
}
// A declaration with the right basename was found, so the search ends.
// Return nulls to ensure that it is handled as with a prepended `this.`.
return ScopeLookupResultImpl(getter: null, setter: null);
}
static bool _isStatic(Element? element) {
switch (element) {
case PropertyAccessorElement(:var isStatic) ||
MethodElement(:var isStatic):
return isStatic;
default:
return false;
}
}
}
/// The top-level declarations of the library.
class LibraryDeclarations with _GettersAndSetters {
List<ExtensionElement> extensions = [];
LibraryDeclarations(LibraryElementImpl library) {
library.getters.forEach(_addGetter);
library.enums.forEach(_addGetter);
library.extensions.forEach(_addExtension);
library.extensionTypes.forEach(_addGetter);
library.setters.forEach(_addSetter);
library.topLevelFunctions.forEach(_addGetter);
library.typeAliases.forEach(_addGetter);
library.mixins.forEach(_addGetter);
library.classes.forEach(_addGetter);
// Add implicit 'dart:core' declarations.
if ('${library.uri}' == 'dart:core') {
_addGetter(DynamicElementImpl.instance);
_addGetter(NeverElementImpl.instance);
}
extensions = extensions.toFixedList();
}
/// Returns a getter or setter with the [name].
Element? withName(String name) {
return _getters[name] ?? _setters[name];
}
void _addExtension(ExtensionElement element) {
_addGetter(element);
if (!extensions.contains(element)) {
extensions.add(element);
}
}
}
class LibraryFragmentScope with _FileImportsScope implements Scope {
@override
final LibraryFragmentImpl _libraryFragment;
@override
final LibraryFragmentScope? _enclosingScope;
@override
final PrefixScope _noPrefixScope;
@override
final Map<String, PrefixElementImpl> _prefixElements = {};
/// All libraries that contribute to [accessibleExtensions].
List<LibraryElementImpl>? _importedLibrariesContributingExtensions;
/// The cached result for [accessibleExtensions].
List<ExtensionElement>? _extensions;
/// This field is set temporarily while resolving all files of a library.
/// So, we can track which elements were actually returned, and which imports
/// in which file (including enclosing files) provided these elements.
///
/// When we are done, we remove the tracker, so that it does not use memory
/// when we are not resolving files of this library.
ImportsTracking? _importsTracking;
LibraryFragmentScope(LibraryFragmentImpl libraryFragment)
: _libraryFragment = libraryFragment,
_enclosingScope = libraryFragment.enclosingFragment?.scope,
_noPrefixScope = PrefixScope(
libraryFragment: libraryFragment,
parent: null,
parentSearchedScopes: const [],
libraryImports: libraryFragment.libraryImports,
prefix: null,
) {
for (var prefix in libraryFragment.prefixes) {
if (prefix.name case var name?) {
_prefixElements[name] = prefix;
}
}
_buildPrefixScopes(
imports: libraryFragment.libraryImports,
prefixes: libraryFragment.prefixes,
);
}
/// The extensions accessible within the file of this scope.
List<ExtensionElement> get accessibleExtensions {
if (_extensions case var extensions?) {
return extensions;
}
globalResultRequirements?.record_libraryFragmentScope_accessibleExtensions(
importedLibraries: importedLibrariesContributingExtensions,
);
return _extensions = {
..._libraryFragment.library.libraryDeclarations.extensions,
..._noPrefixScope._extensions,
for (var prefix in _prefixElements.values) ...prefix.scope._extensions,
...?_enclosingScope?.accessibleExtensions,
}.toFixedList();
}
List<LibraryElementImpl> get importedLibrariesContributingExtensions {
return _importedLibrariesContributingExtensions ??= {
..._noPrefixScope._importedLibraries,
for (var prefix in _prefixElements.values)
...prefix.scope._importedLibraries,
...?_enclosingScope?.importedLibrariesContributingExtensions,
}.toFixedList();
}
// TODO(scheglov): this is kludge.
// We should not use the fragment scope for resolving combinators.
// We should use the export scope of the imported library.
void importsTrackingActive(bool value) {
if (_importsTracking case var importsTracking?) {
for (var tracking in importsTracking.map.values) {
tracking.active = value;
}
}
}
void importsTrackingDestroy() {
_noPrefixScope.importsTrackingDestroy();
for (var prefixElement in _prefixElements.values) {
prefixElement.scope.importsTrackingDestroy();
}
_importsTracking = null;
}
ImportsTracking importsTrackingInit() {
return _importsTracking = ImportsTracking(
map: {
null: _noPrefixScope.importsTrackingInit(),
for (var prefixElement in _prefixElements.values)
prefixElement: prefixElement.scope.importsTrackingInit(),
},
);
}
@override
ScopeLookupResult lookup(String id) {
// Try declarations of the whole library.
if (_lookupLibrary(id) case var result?) {
return result;
}
// Try the combined import scope.
var importResult = _lookupCombined(id);
if (importResult != null) {
return importResult;
}
// No result.
return ScopeLookupResultImpl(getter: null, setter: null);
}
/// Records that [element] is used.
///
/// The [accessibleExtensions] include extensions from the enclosing
/// fragments, so we attribute the usage to the nearest fragment that
/// imports [element].
void notifyExtensionUsed(ExtensionElement element) {
for (LibraryFragmentScope? scope = this; scope != null;) {
if (scope._importsTracking?.notifyExtensionUsed(element) ?? false) {
return;
}
scope = scope._enclosingScope;
}
}
ScopeLookupResult? _lookupCombined(String id) {
// Try prefix elements.
if (_libraryFragment.isAllowedAsPrefixName(id)) {
if (_prefixElements[id] case var prefixElement?) {
return ScopeLookupResultImpl(getter: prefixElement, setter: null);
}
}
// Try imports of the library fragment.
var noPrefixResult = _noPrefixScope.lookup(id);
if (noPrefixResult.getter != null || noPrefixResult.setter != null) {
return noPrefixResult;
}
// Try the combined import scope of the enclosing file.
return _enclosingScope?._lookupCombined(id);
}
ScopeLookupResult? _lookupLibrary(String id) {
var libraryDeclarations = _libraryFragment.library.libraryDeclarations;
var libraryGetter = libraryDeclarations._getters[id];
var librarySetter = libraryDeclarations._setters[id];
if (libraryGetter != null || librarySetter != null) {
return ScopeLookupResultImpl(
getter: libraryGetter,
setter: librarySetter,
);
}
return null;
}
}
class LocalScope extends EnclosedScope {
final FeatureSet _featureSet;
LocalScope(super.parent, {required FeatureSet featureSet})
: _featureSet = featureSet;
void add(Element element) {
assert(() {
return element is LocalVariableElement ||
element is LocalFunctionElement ||
// TODO(scheglov): why not FormalParameterScope?
element is FormalParameterElement;
}());
if (element.name == '_') {
if (element is PatternVariableElement ||
_featureSet.isEnabled(Feature.wildcard_variables)) {
return;
}
}
_addGetter(element);
}
void addAll(Iterable<Element> elements) {
for (var element in elements) {
add(element);
}
}
}
class PrefixScope implements Scope {
final LibraryFragmentImpl libraryFragment;
/// The prefix of this scope, `null` for the imports without a prefix.
final PrefixElement? _prefixElement;
/// The scope of the prefix with the same name in an enclosing file, whose
/// names are also accessible through this prefix.
///
/// It is `null` if this prefix, or the nearest prefix with the same name in
/// an enclosing file, is deferred: a deferred prefix provides only the names
/// of its own library. It is also `null` if there is no such prefix, and for
/// the imports without a prefix.
///
/// We search the enclosing files from the nearest one, and stop at the
/// first file that has a prefix with this name, or imports this name
/// without a prefix.
final PrefixScope? _parent;
/// The scopes of the imports without a prefix of the enclosing files that
/// we searched for the name of this prefix to choose [_parent].
///
/// The choice depends on the names that these scopes import, so every time
/// [parent] is read, we record this requirement.
final List<PrefixScope> _parentSearchedScopes;
final List<LibraryImportImpl> _importElements = [];
final List<LibraryElementImpl> _importedLibraries = [];
final Map<String, Element> _getters = {};
final Map<String, Element> _setters = {};
Set<String>? _settersFromDeprecatedExport;
Set<String>? _gettersFromDeprecatedExport;
final Set<ExtensionElement> _extensions = {};
LibraryElement? _deferredLibrary;
ImportsTrackingOfPrefix? _importsTracking;
final PrefixScopeRequirementState _requirementState =
PrefixScopeRequirementState();
PrefixScope({
required this.libraryFragment,
required PrefixScope? parent,
required List<PrefixScope> parentSearchedScopes,
required List<LibraryImportImpl> libraryImports,
required PrefixElement? prefix,
}) : _prefixElement = prefix,
_parent = parent,
_parentSearchedScopes = parentSearchedScopes {
var elementFactory = libraryElement.session.elementFactory;
for (var import in libraryImports) {
var importedUri = import.uri;
if (importedUri is DirectiveUriWithLibraryImpl &&
import.prefix?.element == prefix) {
_importElements.add(import);
var importedLibrary = importedUri.library;
_importedLibraries.add(importedLibrary);
var combinators = import.combinators.build();
for (var entry in importedLibrary.exportEntries) {
if (combinators.allows(entry.name)) {
var element = elementFactory.elementOfReference3(entry.reference);
if (_shouldAdd(importedLibrary, element)) {
_add(element, importedLibrary.isFromDeprecatedExport(entry));
}
}
}
if (import.prefix case var importPrefix?) {
if (importPrefix.isDeferred) {
_deferredLibrary ??= importedLibrary;
}
}
}
}
}
/// The libraries imported into this scope, and into [parent].
///
/// Excludes the libraries of files that don't exist, because their URIs
/// are already reported, and they don't provide any elements.
List<LibraryElementImpl> get libraries {
return {
for (var library in _importedLibraries)
if (!library.isOriginNotExistingFile) library,
...?parent?.libraries,
}.toList();
}
LibraryElementImpl get libraryElement {
return libraryFragment.element;
}
/// Returns [_parent], and records that the result depends on its choice.
PrefixScope? get parent {
if (_prefixElement?.name case var name?) {
for (var scope in _parentSearchedScopes) {
scope._recordLookupRequirement(name);
}
}
return _parent;
}
/// Returns a top-level declaration named [name] of a library imported into
/// this scope or into [parent], if [name] is private, and that library is
/// another library than [libraryElement].
///
/// This is for reporting a failed [lookup] of [name], which doesn't find
/// it, because private names aren't exported.
Element? getPrivateDeclarationOfImportedLibrary(String name) {
if (!name.isPrivateName) {
return null;
}
for (var library in libraries) {
if (library == libraryElement) {
continue;
}
var declaration =
library.getGetter(name) ??
library.getSetter(name) ??
library.getTopLevelFunction(name) ??
library.getClass(name) ??
library.getEnum(name) ??
library.getExtension(name) ??
library.getExtensionType(name) ??
library.getMixin(name) ??
library.getTypeAlias(name);
if (declaration != null) {
return declaration;
}
}
return null;
}
void importsTrackingDestroy() {
_importsTracking = null;
}
ImportsTrackingOfPrefix importsTrackingInit() {
return _importsTracking = ImportsTrackingOfPrefix(scope: this);
}
@override
ScopeLookupResult lookup(String id) {
var deferredLibrary = _deferredLibrary;
if (deferredLibrary != null &&
id == TopLevelFunctionElement.LOAD_LIBRARY_NAME) {
return ScopeLookupResultImpl(
getter: deferredLibrary.loadLibraryFunction,
setter: null,
);
}
_recordLookupRequirement(id);
var getter = _getters[id];
var setter = _setters[id];
if (getter != null || setter != null) {
_importsTracking?.lookupResult(getter);
_importsTracking?.lookupResult(setter);
return PrefixScopeLookupResult(
getter: getter,
setter: setter,
getterIsFromDeprecatedExport:
_gettersFromDeprecatedExport?.contains(id) ?? false,
setterIsFromDeprecatedExport:
_settersFromDeprecatedExport?.contains(id) ?? false,
);
}
if (parent case var parent?) {
return parent.lookup(id);
}
return ScopeLookupResultImpl(getter: null, setter: null);
}
/// Notifies that the prefix is used without naming an element.
///
/// See [ImportsTrackingOfPrefix.hasPrefixUsedWithoutName].
void notifyPrefixUsedWithoutName() {
_importsTracking?.notifyPrefixUsedWithoutName();
}
void _add(Element element, bool isFromDeprecatedExport) {
if (element is SetterElement) {
_addTo(
element,
isFromDeprecatedExport: isFromDeprecatedExport,
isSetter: true,
);
} else {
_addTo(
element,
isFromDeprecatedExport: isFromDeprecatedExport,
isSetter: false,
);
if (element is ExtensionElement) {
_extensions.add(element);
}
}
}
void _addTo(
Element element, {
required bool isFromDeprecatedExport,
required bool isSetter,
}) {
var map = isSetter ? _setters : _getters;
var id = element.displayName;
var existing = map[id];
if (existing == null) {
map[id] = element;
if (isFromDeprecatedExport) {
if (isSetter) {
(_settersFromDeprecatedExport ??= {}).add(id);
} else {
(_gettersFromDeprecatedExport ??= {}).add(id);
}
}
return;
}
var deprecatedSet = isSetter
? _settersFromDeprecatedExport
: _gettersFromDeprecatedExport;
var wasFromDeprecatedExport = deprecatedSet?.contains(id) ?? false;
if (existing == element) {
if (wasFromDeprecatedExport && !isFromDeprecatedExport) {
deprecatedSet!.remove(id);
}
return;
}
map[id] = _merge(existing, element);
if (wasFromDeprecatedExport) {
deprecatedSet!.remove(id);
}
}
/// Whether the imports of this scope (without [parent]) provide a getter
/// or setter named [id].
///
/// Unlike [lookup], this is not a use of an import, so it is not tracked,
/// and does not record a requirement.
bool _importsName(String id) {
return _getters.containsKey(id) || _setters.containsKey(id);
}
Element _merge(Element existing, Element other) {
if (_isSdkElement(existing)) {
if (!_isSdkElement(other)) {
return other;
}
} else {
if (_isSdkElement(other)) {
return existing;
}
}
var conflictingElements = <Element>{};
_addElement(conflictingElements, existing);
_addElement(conflictingElements, other);
return MultiplyDefinedElementImpl(
libraryFragment,
conflictingElements.first.name!,
conflictingElements.toList(),
);
}
/// Records that [id] was looked up in the imports of this scope.
void _recordLookupRequirement(String id) {
if (globalResultRequirements case var resultRequirements?) {
resultRequirements.record_importPrefixScope_lookup(
state: _requirementState,
importedLibraries: _importedLibraries,
id: id,
);
}
}
bool _shouldAdd(LibraryElementImpl importedLibrary, Element element) {
// It is an error for the identifier `Record`, denoting the `Record` class
// from `dart:core`, where that import scope name is only imported from
// platform libraries, to appear in a library whose language version is
// less than `v`; assuming that `v` is the language version in which
// records are released.
if (!libraryElement.featureSet.isEnabled(Feature.records)) {
if (importedLibrary.isInSdk &&
element is ClassElementImpl &&
element.isDartCoreRecord) {
return false;
}
}
return true;
}
static void _addElement(Set<Element> conflictingElements, Element element) {
if (element is MultiplyDefinedElementImpl) {
conflictingElements.addAll(element.conflictingElements);
} else {
conflictingElements.add(element);
}
}
static bool _isSdkElement(Element element) {
if (element is DynamicElementImpl || element is NeverElementImpl) {
return true;
}
if (element is MultiplyDefinedElement) {
return false;
}
return element.library!.isInSdk;
}
}
class PrefixScopeLookupResult extends ScopeLookupResultImpl {
static const int getterIsFromDeprecatedExportBit = 1 << 0;
static const int setterIsFromDeprecatedExportBit = 1 << 1;
final int _deprecatedBits;
PrefixScopeLookupResult({
required super.getter,
required super.setter,
required bool getterIsFromDeprecatedExport,
required bool setterIsFromDeprecatedExport,
}) : _deprecatedBits =
(getterIsFromDeprecatedExport
? getterIsFromDeprecatedExportBit
: 0) |
(setterIsFromDeprecatedExport ? setterIsFromDeprecatedExportBit : 0);
/// This flag is set to `true` if [getter] is available using import
/// directives where every imported library re-exports the element, and
/// every such `export` directive is marked as deprecated.
bool get getterIsFromDeprecatedExport =>
(_deprecatedBits & getterIsFromDeprecatedExportBit) != 0;
/// This flag is set to `true` if [setter] is available using import
/// directives where every imported library re-exports the element, and
/// every such `export` directive is marked as deprecated.
bool get setterIsFromDeprecatedExport =>
(_deprecatedBits & setterIsFromDeprecatedExportBit) != 0;
}
/// The primary parameter scope.
///
/// It includes every primary formal parameter which is not declaring, not
/// initializing, and not a super parameter.
class PrimaryParameterScope extends EnclosedScope {
PrimaryParameterScope(
super.parent,
ConstructorElement element,
List<FormalParameterFragmentImpl> formalParameterFragments,
) {
assert(element.isPrimary);
var hasWildcardVariables = element.library.featureSet.isEnabled(
Feature.wildcard_variables,
);
for (var fragment in formalParameterFragments) {
if (fragment.isOriginOtherFragmentOfEnclosing ||
fragment is FieldFormalParameterFragmentImpl ||
fragment is SuperFormalParameterFragmentImpl) {
continue;
}
var name = fragment.name;
if (name == null || hasWildcardVariables && name == '_') {
continue;
}
_getters[name] ??= fragment.element;
}
}
}
class ScopeLookupResultImpl extends ScopeLookupResult {
@override
final Element? getter;
@override
final Element? setter;
ScopeLookupResultImpl({required this.getter, required this.setter});
}
class TypeParameterScope extends EnclosedScope {
TypeParameterScope(
super.parent,
List<TypeParameterElement> elements, {
required FeatureSet featureSet,
}) {
var wildcardVariablesEnabled = featureSet.isEnabled(
Feature.wildcard_variables,
);
for (var element in elements) {
if (wildcardVariablesEnabled && element.name == '_') {
continue;
}
_addGetter(element);
}
}
}
/// The scope of the imports, or of the doc imports, of a file.
///
/// Doc imports are scoped like imports, so the prefixes of both extend the
/// prefixes of the enclosing files by the same rules, implemented once here.
mixin _FileImportsScope {
/// The scope of the same kind of the enclosing file.
_FileImportsScope? get _enclosingScope;
/// The file of this scope.
LibraryFragmentImpl get _libraryFragment;
/// The scope of the names imported without a prefix.
PrefixScope get _noPrefixScope;
/// The prefixes declared in this file, by name.
Map<String, PrefixElementImpl> get _prefixElements;
/// Sets the scope of each of the [prefixes] declared in this file, built
/// from those of the [imports] that use it.
void _buildPrefixScopes({
required List<LibraryImportImpl> imports,
required Iterable<PrefixElementImpl> prefixes,
}) {
for (var prefix in prefixes) {
var (:parent, :searchedScopes) = _getParentPrefixScope(prefix);
prefix.scope = PrefixScope(
libraryFragment: _libraryFragment,
parent: parent,
parentSearchedScopes: searchedScopes,
libraryImports: imports,
prefix: prefix,
);
}
}
/// Returns the scope of the prefix of an enclosing file that [prefix]
/// extends, and the scopes that we searched to find it.
///
/// A deferred prefix extends nothing. Otherwise we search the enclosing
/// files from the nearest one, and stop at the first file that has a prefix
/// with this name, or imports this name without a prefix. If that prefix is
/// deferred, [prefix] extends nothing either.
({PrefixScope? parent, List<PrefixScope> searchedScopes})
_getParentPrefixScope(PrefixElementImpl prefix) {
var name = prefix.name;
if (name == null || prefix.isDeferred) {
return (parent: null, searchedScopes: const []);
}
var searchedScopes = <PrefixScope>[];
for (
var scope = _enclosingScope;
scope != null;
scope = scope._enclosingScope
) {
if (scope._prefixElements[name] case var parentPrefix?) {
return (
parent: parentPrefix.isDeferred ? null : parentPrefix.scope,
searchedScopes: searchedScopes,
);
}
searchedScopes.add(scope._noPrefixScope);
if (scope._noPrefixScope._importsName(name)) {
break;
}
}
return (parent: null, searchedScopes: searchedScopes);
}
}
mixin _GettersAndSetters {
final Map<String, Element> _getters = {};
final Map<String, Element> _setters = {};
void _addGetter(Element element) {
var id = element.lookupName;
if (id != null) {
_getters[id] ??= element;
}
}
void _addSetter(Element element) {
var name = element.lookupName;
if (name != null && name.endsWith('=')) {
var id = considerCanonicalizeString(name.substring(0, name.length - 1));
_setters[id] ??= element;
}
}
}
extension on LibraryFragmentImpl {
/// Whether [id] can be the name of an import prefix.
///
/// With wildcard variables, `as _` does not declare a prefix.
bool isAllowedAsPrefixName(String id) {
if (id == '_') {
return !library.featureSet.isEnabled(Feature.wildcard_variables);
}
return true;
}
}