Report not initialized potentially non-nullable instance fields.

R=brianwilkerson@google.com, paulberry@google.com

Change-Id: Id762e0cf651b2f2d7199f3447b5bf305b1a584c8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107567
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
Konstantin Shcheglov 2019-07-23 23:17:32 +00:00 committed by commit-bot@chromium.org
parent 030e60e0ef
commit 48662bdb94
8 changed files with 327 additions and 8 deletions

View file

@ -245,6 +245,8 @@ const List<ErrorCode> errorCodeValues = const [
CompileTimeErrorCode.NON_SYNC_FACTORY,
CompileTimeErrorCode.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
CompileTimeErrorCode.NOT_ENOUGH_REQUIRED_ARGUMENTS,
CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD,
CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR,
CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_STATIC_FIELD,
CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_TOP_LEVEL_VARIABLE,
CompileTimeErrorCode.NOT_ITERABLE_SPREAD,

View file

@ -2554,6 +2554,43 @@ class CompileTimeErrorCode extends ErrorCode {
"{0} required argument(s) expected, but {1} found.",
correction: "Try adding the missing arguments.");
/**
* It is an error if an instance field with potentially non-nullable type has
* no initializer expression and is not initialized in a constructor via an
* initializing formal or an initializer list entry, unless the field is
* marked with the `late` modifier.
*
* Parameters:
* 0: the name of the field that is not initialized
*/
static const CompileTimeErrorCode
NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD = const CompileTimeErrorCode(
'NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD',
"Non-nullable instance field '{0}' must be initialized.",
correction: "Try adding an initializer expression, "
"or a generative constructor that initializes it, "
"or mark it 'late'.");
/**
* It is an error if an instance field with potentially non-nullable type has
* no initializer expression and is not initialized in a constructor via an
* initializing formal or an initializer list entry, unless the field is
* marked with the `late` modifier.
*
* Parameters:
*
* Parameters:
* 0: the name of the field that is not initialized
*/
static const CompileTimeErrorCode
NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR =
const CompileTimeErrorCode(
'NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR',
"Non-nullable instance field '{0}' must be initialized.",
correction: "Try adding an initializer expression, "
"or add a field initializer in this constructor, "
"or mark it 'late'.");
/**
* It is an error if a static variable with potentially non-nullable type has
* no initializer expression.

View file

@ -2013,15 +2013,22 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
}
}
}
// Prepare a list of not initialized fields.
// Prepare lists of not initialized fields.
List<FieldElement> notInitFinalFields = <FieldElement>[];
fieldElementsMap.forEach((FieldElement fieldElement, INIT_STATE state) {
if (state == INIT_STATE.NOT_INIT) {
if (fieldElement.isFinal && !fieldElement.isLate) {
notInitFinalFields.add(fieldElement);
}
List<FieldElement> notInitNonNullableFields = <FieldElement>[];
fieldElementsMap.forEach((FieldElement field, INIT_STATE state) {
if (state != INIT_STATE.NOT_INIT) return;
if (field.isLate) return;
if (field.isFinal) {
notInitFinalFields.add(field);
} else if (_isNonNullable &&
_typeSystem.isPotentiallyNonNullable(field.type)) {
notInitNonNullableFields.add(field);
}
});
// Visit all of the states in the map to ensure that none were never
// initialized.
fieldElementsMap.forEach((FieldElement fieldElement, INIT_STATE state) {
@ -2055,6 +2062,17 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
[names[0], names[1], names.length - 2]);
}
}
if (notInitNonNullableFields.isNotEmpty) {
var names = notInitNonNullableFields.map((f) => f.name).toList()..sort();
for (var name in names) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR,
constructor.returnType,
[name]);
}
}
}
/**
@ -3419,7 +3437,9 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
}
for (ClassMember classMember in members) {
if (classMember is FieldDeclaration) {
_checkForFinalNotInitialized(classMember.fields);
var fields = classMember.fields;
_checkForFinalNotInitialized(fields);
_checkForNotInitializedNonNullableInstanceFields(classMember);
}
}
}
@ -4708,6 +4728,31 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
}
}
void _checkForNotInitializedNonNullableInstanceFields(
FieldDeclaration fieldDeclaration,
) {
if (!_isNonNullable) return;
if (fieldDeclaration.isStatic) return;
var fields = fieldDeclaration.fields;
if (fields.isLate) return;
if (fields.isFinal) return;
for (var field in fields.variables) {
if (field.initializer != null) continue;
var type = field.declaredElement.type;
if (!_typeSystem.isPotentiallyNonNullable(type)) continue;
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD,
field,
[field.name.name],
);
}
}
void _checkForNotInitializedNonNullableStaticField(FieldDeclaration node) {
if (!_isNonNullable) return;

View file

@ -2937,6 +2937,10 @@ class TypeSystemTest extends AbstractTypeSystemTest {
expect(typeSystem.isNonNullable(starType), true);
}
test_isNonNullable_Never() {
expect(typeSystem.isNonNullable(neverType), true);
}
test_isNonNullable_never() {
expect(typeSystem.isNonNullable(neverType), true);
}
@ -3115,6 +3119,10 @@ class TypeSystemTest extends AbstractTypeSystemTest {
expect(typeSystem.isNullable(starType), false);
}
test_isNullable_Never() {
expect(typeSystem.isNullable(neverType), false);
}
test_isNullable_never() {
expect(typeSystem.isNullable(neverType), false);
}

View file

@ -0,0 +1,184 @@
// 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/experiments.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/driver_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(NotInitializedNonNullableInstanceFieldTest);
});
}
@reflectiveTest
class NotInitializedNonNullableInstanceFieldTest extends DriverResolutionTest {
@override
AnalysisOptionsImpl get analysisOptions =>
AnalysisOptionsImpl()..enabledExperiments = [EnableString.non_nullable];
test_constructorFieldInitializer() async {
await assertNoErrorsInCode('''
class A {
int x;
A() : x = 0;
}
''');
}
test_factoryConstructor() async {
await assertNoErrorsInCode('''
class A {
int x = 0;
A(this.x);
factory A.named() => A(0);
}
''');
}
test_fieldFormal() async {
await assertNoErrorsInCode('''
class A {
int x;
A(this.x);
}
''');
}
test_futureOr_questionArgument_none() async {
await assertNoErrorsInCode('''
import 'dart:async';
class A {
FutureOr<int?> x;
}
''');
}
test_hasInitializer() async {
await assertNoErrorsInCode('''
class A {
int x = 0;
}
''');
}
test_inferredType() async {
await assertErrorsInCode('''
abstract class A {
int get x;
}
class B extends A {
var x;
}
''', [
error(CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD,
61, 1),
]);
}
test_notAllConstructors() async {
await assertErrorsInCode('''
class A {
int x;
A.a(this.x);
A.b();
}
''', [
error(
CompileTimeErrorCode
.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR,
38,
1),
]);
}
test_notAllFields() async {
await assertErrorsInCode('''
class A {
int x, y, z;
A() : x = 0, z = 2;
}
''', [
error(
CompileTimeErrorCode
.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD_CONSTRUCTOR,
28,
1),
]);
}
test_nullable() async {
await assertNoErrorsInCode('''
class A {
int? x;
}
''');
}
test_type_dynamic() async {
await assertNoErrorsInCode('''
class A {
dynamic x;
}
''');
}
test_type_dynamic_implicit() async {
await assertNoErrorsInCode('''
class A {
var x;
}
''');
}
test_type_never() async {
await assertErrorsInCode('''
class A {
Never x;
}
''', [
error(CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD,
18, 1),
]);
}
test_type_void() async {
await assertNoErrorsInCode('''
class A {
void x;
}
''');
}
test_typeParameter() async {
await assertErrorsInCode('''
class A<T> {
T x;
}
''', [
error(CompileTimeErrorCode.NOT_INITIALIZED_NON_NULLABLE_INSTANCE_FIELD,
17, 1),
]);
}
test_typeParameter_nullable() async {
await assertNoErrorsInCode('''
class A<T> {
T? x;
}
''');
}
}

View file

@ -138,6 +138,8 @@ import 'non_constant_spread_expression_from_deferred_library_test.dart'
import 'non_null_opt_out_test.dart' as non_null_opt_out;
import 'not_assigned_potentially_non_nullable_local_variable_test.dart'
as not_assigned_potentially_non_nullable_local_variable;
import 'not_initialized_non_nullable_instance_field_test.dart'
as not_initialized_non_nullable_instance_field;
import 'not_initialized_non_nullable_static_field_test.dart'
as not_initialized_non_nullable_static_field;
import 'not_initialized_non_nullable_top_level_variable_test.dart'
@ -319,6 +321,7 @@ main() {
non_constant_spread_expression_from_deferred_library.main();
non_null_opt_out.main();
not_assigned_potentially_non_nullable_local_variable.main();
not_initialized_non_nullable_instance_field.main();
not_initialized_non_nullable_static_field.main();
not_initialized_non_nullable_top_level_variable.main();
not_iterable_spread.main();

View file

@ -0,0 +1,40 @@
// 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.
// SharedOptions=--enable-experiment=non-nullable
// Test that it is an error if an instance field with potentially non-nullable
// type has no initializer expression and is not initialized in a constructor
// via an initializing formal or an initializer list entry, unless the field is
// marked with the `late` modifier.
void main() {}
class A {
int v = 0; //# 01: ok
int? v; //# 02: ok
int? v = 0; //# 03: ok
dynamic v; //# 04: ok
var v; //# 05: ok
void v; //# 06: ok
int v; A(this.v); //# 07: ok
int v; A() : v = 0; //# 08: ok
int v; //# 09: compile-time error
Never v; //# 10: compile-time error
int v; A(); //# 11: compile-time error
int v; A(this.v); A.second(); //# 12: compile-time error
}
class B<T> {
T v; //# 13: compile-time error
T? v; //# 14: ok
T v; B(this.v); //# 15: ok
}

View file

@ -15,7 +15,7 @@ int f1(
required //# 03: compile-time error
class C1 {
required //# 04: compile-time error
int f2;
int f2 = 0;
}
// Duplicate modifier