blob: 4e1a0fa6dd214c7c2cd7210d1fb9fbd19ffafcba [file] [log] [blame] [edit]
// 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/dart/element/scope.dart';
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,
);
List<FormalParameterElementImpl>? inScopePrimaryConstructorParameters;
if (element case FieldElementImpl field) {
if (field.isInstanceField && !field.isLate) {
inScopePrimaryConstructorParameters = field.enclosingElement
.tryCast<InterfaceElementImpl>()
?.primaryConstructor
?.formalParameters;
}
}
astResolver.resolveExpression(
() => node.initializer!,
contextType: element.type,
inScopePrimaryConstructorParameters: inScopePrimaryConstructorParameters,
);
// 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 interfacesToInfer = linker.builders.values.expand((builder) {
return builder.element.children.whereType<InterfaceElementImpl>();
}).toList();
var inferrer = InstanceMemberInferrer(linker.inheritance);
inferrer.perform(interfacesToInfer);
}
}
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.isOriginGetterSetter) {
return;
}
if (!element.hasImplicitType) return;
_toInfer.add(element);
element.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() {
LibraryFragmentImpl? initializerLibraryFragment;
Scope? scope;
ExpressionImpl Function()? getInitializer;
List<FormalParameterElementImpl>? inScopePrimaryConstructorParameters;
for (var fragment in _element.fragments) {
var node = _linker.elementNodes[fragment];
switch (node) {
case VariableDeclarationImpl():
if (node.initializer != null) {
initializerLibraryFragment = fragment.libraryFragment;
scope = LinkingNodeContext.get(node).scope;
getInitializer = () => node.initializer!;
if (_element case FieldElementImpl field) {
if (field.isInstanceField && !field.isLate) {
inScopePrimaryConstructorParameters = field.enclosingElement
.tryCast<InterfaceElementImpl>()
?.primaryConstructor
?.formalParameters;
}
}
}
case DefaultFormalParameterImpl():
_assertElementFieldOriginDeclaringFormalParameter();
if (node.defaultValue != null) {
initializerLibraryFragment = fragment.libraryFragment;
scope = LinkingNodeContext.get(node).scope;
getInitializer = () => node.defaultValue!;
} else {
_status = _InferenceStatus.inferred;
return _element.library.typeSystem.objectQuestion;
}
case SimpleFormalParameterImpl():
_assertElementFieldOriginDeclaringFormalParameter();
_status = _InferenceStatus.inferred;
return _element.library.typeSystem.objectQuestion;
}
}
if (initializerLibraryFragment == null ||
scope == null ||
getInitializer == 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 = TopLevelInferenceErrorDependencyCycle(
cycle: cycle.map((e) => e._element.name ?? '').sorted(),
);
for (var inference in cycle) {
if (inference._status == _InferenceStatus.beingInferred) {
var element = inference._element;
element.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
.tryCast<InterfaceElementImpl>();
var analysisOptions = _libraryBuilder.kind.file.analysisOptions;
var astResolver = AstResolver(
_linker,
initializerLibraryFragment,
scope,
analysisOptions,
enclosingClassElement: enclosingInterfaceElement,
);
astResolver.resolveExpression(
getInitializer,
inScopePrimaryConstructorParameters: inScopePrimaryConstructorParameters,
);
// 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 = getInitializer().typeOrThrow;
return _refineType(initializerType);
}
void _assertElementFieldOriginDeclaringFormalParameter() {
assert(() {
if (_element case FieldElementImpl element) {
return element.isOriginDeclaringFormalParameter;
}
return false;
}());
}
TypeImpl _refineType(TypeImpl type) {
if (type.isDartCoreNull) {
// When `T` is `Null`, `p` has declared type `Object?`.
if (_element case FieldElementImpl field) {
if (field.declaringFormalParameter != null) {
return _element.library.typeSystem.objectQuestion;
}
}
// Logic for older language versions.
return DynamicTypeImpl.instance;
}
return type;
}
}