Disallow uninitialized non-nullable variables

Declaring a non-nullable variable without initializing it is a static
error. If a constant is declared instead (either run-time or
compile-time) the error handling logic is the same as before.

BUG=
R=jmesserly@google.com

Review URL: https://codereview.chromium.org/2219653005 .
This commit is contained in:
Stan Manilov 2016-08-05 11:07:46 -07:00
parent b2c771263f
commit f5f4bb4ebd
3 changed files with 67 additions and 6 deletions

View file

@ -2743,6 +2743,7 @@ abstract class ErrorCode {
StaticTypeWarningCode.NON_BOOL_EXPRESSION,
StaticTypeWarningCode.NON_BOOL_NEGATION_EXPRESSION,
StaticTypeWarningCode.NON_BOOL_OPERAND,
StaticTypeWarningCode.NON_NULLABLE_FIELD_NOT_INITIALIZED,
StaticTypeWarningCode.NON_TYPE_AS_TYPE_ARGUMENT,
StaticTypeWarningCode.RETURN_OF_INVALID_TYPE,
StaticTypeWarningCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS,
@ -4280,6 +4281,14 @@ class StaticTypeWarningCode extends ErrorCode {
const StaticTypeWarningCode('NON_BOOL_OPERAND',
"The operands of the '{0}' operator must be assignable to 'bool'");
/**
*
*/
static const StaticTypeWarningCode NON_NULLABLE_FIELD_NOT_INITIALIZED =
const StaticTypeWarningCode(
'NON_NULLABLE_FIELD_NOT_INITIALIZED',
"Variable '{0}' of non-nullable type '{1}' must be initialized");
/**
* 15.8 Parameterized Types: It is a static type warning if <i>A<sub>i</sub>,
* 1 &lt;= i &lt;= n</i> does not denote a type in the enclosing lexical scope.

View file

@ -628,6 +628,20 @@ class CodeChecker extends RecursiveAstVisitor {
node.visitChildren(this);
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
if (!node.isConst &&
!node.isFinal &&
node.initializer == null &&
rules.isNonNullableType(node?.element?.type)) {
_recordMessage(
node,
StaticTypeWarningCode.NON_NULLABLE_FIELD_NOT_INITIALIZED,
[node.name, node?.element?.type]);
}
return super.visitVariableDeclaration(node);
}
@override
void visitWhileStatement(WhileStatement node) {
checkBoolean(node.condition);

View file

@ -48,12 +48,6 @@ main() {
checkFile('int x = null;');
}
void test_uninitialized_nonnullable() {
// If `int`s are non-nullable, then this code should throw an error.
addFile('int x;');
check(nonnullableTypes: <String>['dart:core,int']);
}
void test_initialize_nonnullable_with_null() {
addFile('int x = /*error:INVALID_ASSIGNMENT*/null;');
check(nonnullableTypes: <String>['dart:core,int']);
@ -76,6 +70,50 @@ main() {
check(nonnullableTypes: <String>['dart:core,int']);
}
void test_uninitialized_nonnullable_local_variable() {
// Ideally, we will do flow analysis and throw an error only if a variable
// is used before it has been initialized.
addFile('main() { int /*error:NON_NULLABLE_FIELD_NOT_INITIALIZED*/x; }');
check(nonnullableTypes: <String>['dart:core,int']);
}
void test_uninitialized_nonnullable_top_level_variable_declaration() {
// If `int`s are non-nullable, then this code should throw an error.
addFile('int /*error:NON_NULLABLE_FIELD_NOT_INITIALIZED*/x;');
check(nonnullableTypes: <String>['dart:core,int']);
}
void test_uninitialized_nonnullable_field_declaration() {
addFile('''
void foo() {}
class A {
// Ideally, we should allow x to be init in the constructor, but that requires
// too much complication in the checker, so for now we throw a static error at
// the declaration site.
int /*error:NON_NULLABLE_FIELD_NOT_INITIALIZED*/x;
A();
}
''');
check(nonnullableTypes: <String>['dart:core,int']);
}
void test_prefer_final_to_non_nullable_error() {
addFile('main() { final int /*error:FINAL_NOT_INITIALIZED*/x; }');
addFile('final int /*error:FINAL_NOT_INITIALIZED*/x;');
addFile('''
void foo() {}
class A {
final int x;
/*warning:FINAL_NOT_INITIALIZED_CONSTRUCTOR_1*/A();
}
''');
check(nonnullableTypes: <String>['dart:core,int']);
}
// Default example from NNBD document.
final String defaultNnbdExample = '''
class Point {