blob: 00fadde7e67f85f6cbf430c03e42d00297d0b667 [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/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/codes.dart';
/// Verifier for initializing fields in constructors.
class ConstructorFieldsVerifier {
final TypeSystemImpl _typeSystem;
final ErrorReporter _errorReporter;
bool _isInNativeClass = false;
/// When a new class or mixin is entered, [_initFieldsMap] initializes this
/// map, and [leaveClassOrMixin] resets it.
///
/// [_InitState.notInit] or [_InitState.initInDeclaration] is set for each
/// field. Later [verify] is called to verify each constructor of the class.
Map<FieldElement, _InitState>? _initialFieldMap;
/// The state of fields in the current constructor.
Map<FieldElement, _InitState> _fieldMap = {};
/// Set to `true` if the current constructor redirects.
bool _hasRedirectingConstructorInvocation = false;
ConstructorFieldsVerifier({
required TypeSystemImpl typeSystem,
required ErrorReporter errorReporter,
}) : _typeSystem = typeSystem,
_errorReporter = errorReporter;
void enterClass(ClassDeclaration node) {
_isInNativeClass = node.nativeClause != null;
_initFieldsMap(node.declaredElement!);
}
void enterEnum(EnumDeclaration node) {
_isInNativeClass = false;
_initFieldsMap(node.declaredElement!);
}
void leaveClass() {
_isInNativeClass = false;
_initialFieldMap = null;
}
/// Verify that the given [node] declaration does not violate any of
/// the error codes relating to the initialization of fields in the
/// enclosing class.
void verify(ConstructorDeclaration node) {
if (node.factoryKeyword != null ||
node.redirectedConstructor != null ||
node.externalKeyword != null) {
return;
}
if (node.parent is! ClassDeclaration) {
return;
}
if (_isInNativeClass) {
return;
}
_fieldMap = Map.of(_initialFieldMap!);
_hasRedirectingConstructorInvocation = false;
_updateWithParameters(node);
_updateWithInitializers(node);
if (_hasRedirectingConstructorInvocation) {
return;
}
// Prepare lists of not initialized fields.
List<FieldElement> notInitFinalFields = <FieldElement>[];
List<FieldElement> notInitNonNullableFields = <FieldElement>[];
_fieldMap.forEach((FieldElement field, _InitState state) {
if (state != _InitState.notInit) return;
if (field.isLate) return;
if (field.isAbstract || field.isExternal) return;
if (field.isFinal) {
notInitFinalFields.add(field);
} else if (_typeSystem.isNonNullableByDefault &&
_typeSystem.isPotentiallyNonNullable(field.type)) {
notInitNonNullableFields.add(field);
}
});
_reportNotInitializedFinal(node, notInitFinalFields);
_reportNotInitializedNonNullable(node, notInitNonNullableFields);
}
void _initFieldsMap(ClassElement element) {
_initialFieldMap = <FieldElement, _InitState>{};
for (var field in element.fields) {
if (!field.isSynthetic) {
_initialFieldMap![field] = field.hasInitializer
? _InitState.initInDeclaration
: _InitState.notInit;
}
}
}
void _reportNotInitializedFinal(
ConstructorDeclaration node,
List<FieldElement> notInitFinalFields,
) {
if (notInitFinalFields.isEmpty) {
return;
}
var names = notInitFinalFields.map((item) => item.name).toList();
names.sort();
if (names.length == 1) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_1,
node.returnType,
names,
);
} else if (names.length == 2) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_2,
node.returnType,
names,
);
} else {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_3_PLUS,
node.returnType,
[names[0], names[1], names.length - 2],
);
}
}
void _reportNotInitializedNonNullable(
ConstructorDeclaration node,
List<FieldElement> notInitNonNullableFields,
) {
if (notInitNonNullableFields.isEmpty) {
return;
}
var names = notInitNonNullableFields.map((f) => f.name).toList();
names.sort();
for (var name in names) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR,
node.returnType,
[name],
);
}
}
void _updateWithInitializers(ConstructorDeclaration node) {
for (var initializer in node.initializers) {
if (initializer is RedirectingConstructorInvocation) {
_hasRedirectingConstructorInvocation = true;
}
if (initializer is ConstructorFieldInitializer) {
SimpleIdentifier fieldName = initializer.fieldName;
var element = fieldName.staticElement;
if (element is FieldElement) {
var state = _fieldMap[element];
if (state == _InitState.notInit) {
_fieldMap[element] = _InitState.initInInitializer;
} else if (state == _InitState.initInDeclaration) {
if (element.isFinal || element.isConst) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.FIELD_INITIALIZED_IN_INITIALIZER_AND_DECLARATION,
fieldName,
);
}
} else if (state == _InitState.initInFieldFormal) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.FIELD_INITIALIZED_IN_PARAMETER_AND_INITIALIZER,
fieldName,
);
} else if (state == _InitState.initInInitializer) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZED_BY_MULTIPLE_INITIALIZERS,
fieldName,
[element.displayName],
);
}
}
}
}
}
void _updateWithParameters(ConstructorDeclaration node) {
var formalParameters = node.parameters.parameters;
for (FormalParameter parameter in formalParameters) {
parameter = _baseParameter(parameter);
if (parameter is FieldFormalParameter) {
var fieldElement =
(parameter.declaredElement as FieldFormalParameterElementImpl)
.field;
if (fieldElement == null) {
continue;
}
_InitState? state = _fieldMap[fieldElement];
if (state == _InitState.notInit) {
_fieldMap[fieldElement] = _InitState.initInFieldFormal;
} else if (state == _InitState.initInDeclaration) {
if (fieldElement.isFinal || fieldElement.isConst) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.FINAL_INITIALIZED_IN_DECLARATION_AND_CONSTRUCTOR,
parameter.identifier,
[fieldElement.displayName],
);
}
} else if (state == _InitState.initInFieldFormal) {
// Reported in DuplicateDefinitionVerifier._checkDuplicateIdentifier
}
}
}
}
static FormalParameter _baseParameter(FormalParameter parameter) {
if (parameter is DefaultFormalParameter) {
return parameter.parameter;
}
return parameter;
}
}
/// The four states of a field initialization state through a constructor
/// signature, not initialized, initialized in the field declaration,
/// initialized in the field formal, and finally, initialized in the
/// initializers list.
enum _InitState {
/// The field is declared without an initializer.
notInit,
/// The field is declared with an initializer.
initInDeclaration,
/// The field is initialized in a field formal parameter of the constructor
/// being verified.
initInFieldFormal,
/// The field is initialized in the list of initializers of the constructor
/// being verified.
initInInitializer,
}