blob: 9accb8f130bb3310e84b559e0c8fa0f5c23da7bf [file] [log] [blame]
// Copyright (c) 2019, 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:analyzer/src/dart/analysis/analysis_options.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/inference_error.dart';
import 'package:analyzer/src/summary2/ast_resolver.dart';
import 'package:analyzer/src/summary2/instance_member_inferrer.dart';
import 'package:analyzer/src/summary2/library_builder.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/linking_node_scope.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
import 'package:collection/collection.dart';
/// Resolver for typed constant top-level variables and fields initializers.
///
/// Initializers of untyped variables are resolved during [TopLevelInference].
class ConstantInitializersResolver {
final Linker linker;
ConstantInitializersResolver(this.linker);
void perform() {
for (var builder in linker.builders.values) {
var analysisOptions = builder.kind.file.analysisOptions;
var libraryElement = builder.element;
var instanceElementListList = [
libraryElement.classes,
libraryElement.enums,
libraryElement.extensions,
libraryElement.extensionTypes,
libraryElement.mixins,
];
for (var instanceElementList in instanceElementListList) {
for (var instanceElement in instanceElementList) {
for (var field in instanceElement.fields) {
_resolveVariable(analysisOptions, field);
}
}
}
for (var variable in libraryElement.topLevelVariables) {
_resolveVariable(analysisOptions, variable);
}
}
}
void _resolveVariable(
AnalysisOptionsImpl analysisOptions,
PropertyInducingElementImpl element,
) {
if (element is FieldElementImpl && element.isEnumConstant) {
return;
}
var constantInitializer = element.constantInitializer2;
if (constantInitializer == null) {
return;
}
var fragment = constantInitializer.fragment;
var node = linker.elementNodes[fragment] as VariableDeclarationImpl;
var scope = LinkingNodeContext.get(node).scope;
var astResolver = AstResolver(
linker,
fragment.libraryFragment as LibraryFragmentImpl,
scope,
analysisOptions,
);
astResolver.resolveExpression(
() => node.initializer!,
contextType: element.type,
);
// We could have rewritten the initializer.
fragment.constantInitializer = node.initializer;
}
}
class TopLevelInference {
final Linker linker;
TopLevelInference(this.linker);
void infer() {
var initializerInference = _InitializerInference(linker);
initializerInference.createNodes();
_performOverrideInference();
initializerInference.perform();
}
void _performOverrideInference() {
var inferrer = InstanceMemberInferrer(linker.inheritance);
for (var builder in linker.builders.values) {
inferrer.inferLibrary(builder.element);
}
}
}
enum _InferenceStatus { notInferred, beingInferred, inferred }
class _InitializerInference {
final Linker _linker;
final List<PropertyInducingElementImpl> _toInfer = [];
final List<_PropertyInducingElementTypeInference> _inferring = [];
late LibraryBuilder _libraryBuilder;
_InitializerInference(this._linker);
void createNodes() {
for (var builder in _linker.builders.values) {
_libraryBuilder = builder;
var libraryElement = builder.element;
var instanceElementListList = [
libraryElement.classes,
libraryElement.enums,
libraryElement.extensions,
libraryElement.extensionTypes,
libraryElement.mixins,
];
for (var instanceElementList in instanceElementListList) {
for (var instanceElement in instanceElementList) {
for (var field in instanceElement.fields) {
_addVariableNode(field);
}
}
}
for (var variable in libraryElement.topLevelVariables) {
_addVariableNode(variable);
}
}
}
/// Perform type inference for variables for which it was not done yet.
void perform() {
for (var element in _toInfer) {
// Will perform inference, if not done yet.
element.type;
}
}
void _addVariableNode(PropertyInducingElementImpl element) {
if (element.isSynthetic &&
!(element is FieldElementImpl && element.isSyntheticEnumField)) {
return;
}
if (!element.hasImplicitType) return;
_toInfer.add(element);
element.firstFragment.typeInference = _PropertyInducingElementTypeInference(
_linker,
_inferring,
element,
_libraryBuilder,
);
}
}
class _PropertyInducingElementTypeInference
implements PropertyInducingElementTypeInference {
final Linker _linker;
/// The stack of objects performing inference now. A new object is pushed
/// when we start resolving the initializer, and popped when we are done.
final List<_PropertyInducingElementTypeInference> _inferring;
/// The status is used to identify a cycle, when we are asked to infer the
/// type, but the status is already [_InferenceStatus.beingInferred].
_InferenceStatus _status = _InferenceStatus.notInferred;
final LibraryBuilder _libraryBuilder;
final PropertyInducingElementImpl _element;
_PropertyInducingElementTypeInference(
this._linker,
this._inferring,
this._element,
this._libraryBuilder,
);
@override
TypeImpl perform() {
PropertyInducingFragmentImpl? initializerFragment;
VariableDeclarationImpl? variableDeclaration;
for (var fragment in _element.fragments) {
var node = _linker.elementNodes[fragment] as VariableDeclarationImpl;
if (node.initializer != null) {
initializerFragment = fragment;
variableDeclaration = node;
}
}
if (initializerFragment == null || variableDeclaration == null) {
_status = _InferenceStatus.inferred;
return DynamicTypeImpl.instance;
}
// With this status the type must be already set.
// So, the element knows the type, ans should not call the inferrer.
if (_status == _InferenceStatus.inferred) {
assert(false, 'Should not happen: $_element');
return DynamicTypeImpl.instance;
}
// If we are already inferring this element, we found a cycle.
if (_status == _InferenceStatus.beingInferred) {
var startIndex = _inferring.indexOf(this);
var cycle = _inferring.slice(startIndex);
var inferenceError = TopLevelInferenceError(
kind: TopLevelInferenceErrorKind.dependencyCycle,
arguments: cycle.map((e) => e._element.name ?? '').sorted(),
);
for (var inference in cycle) {
if (inference._status == _InferenceStatus.beingInferred) {
var element = inference._element;
element.firstFragment.typeInferenceError = inferenceError;
element.type = DynamicTypeImpl.instance;
inference._status = _InferenceStatus.inferred;
}
}
return DynamicTypeImpl.instance;
}
assert(_status == _InferenceStatus.notInferred);
// Push self into the stack, and mark.
_inferring.add(this);
_status = _InferenceStatus.beingInferred;
var enclosingElement = _element.enclosingElement;
var enclosingInterfaceElement =
enclosingElement.ifTypeOrNull<InterfaceElementImpl>();
var scope = LinkingNodeContext.get(variableDeclaration).scope;
var analysisOptions = _libraryBuilder.kind.file.analysisOptions;
var astResolver = AstResolver(
_linker,
initializerFragment.libraryFragment as LibraryFragmentImpl,
scope,
analysisOptions,
enclosingClassElement: enclosingInterfaceElement,
);
astResolver.resolveExpression(() => variableDeclaration!.initializer!);
// Pop self from the stack.
var self = _inferring.removeLast();
assert(identical(self, this));
// We might have found a cycle, and already set the type.
// Anyway, we are done.
if (_status == _InferenceStatus.inferred) {
return _element.type;
} else {
_status = _InferenceStatus.inferred;
}
var initializerType = variableDeclaration.initializer!.typeOrThrow;
return _refineType(initializerType);
}
TypeImpl _refineType(TypeImpl type) {
if (type.isDartCoreNull) {
return DynamicTypeImpl.instance;
}
return type;
}
}