| // 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, |
| } |