Version 2.17.0-156.0.dev
Merge commit 'ed68a37df757c3dff4801e52ce8b48ae60688471' into 'dev'
diff --git a/DEPS b/DEPS
index 7e3aaea..276e2bd 100644
--- a/DEPS
+++ b/DEPS
@@ -170,7 +170,7 @@
"watcher_rev": "f76997ab0c857dc5537ac0975a9ada92b54ef949",
"webdriver_rev": "ff5ccb1522edf4bed578ead4d65e0cbc1f2c4f02",
"web_components_rev": "8f57dac273412a7172c8ade6f361b407e2e4ed02",
- "web_socket_channel_rev": "6448ce532445a8a458fa191d9346df071ae0acad",
+ "web_socket_channel_rev": "99dbdc5769e19b9eeaf69449a59079153c6a8b1f",
"WebCore_rev": "bcb10901266c884e7b3740abc597ab95373ab55c",
"webdev_rev": "832b096c0c24798d3df46faa7b7661fe930573c2",
"webkit_inspection_protocol_rev": "dd6fb5d8b536e19cedb384d0bbf1f5631923f1e8",
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart b/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart
index 75e7dd0a..838e6a2 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart
@@ -48,9 +48,6 @@
}
} else if (element is ParameterElement) {
if (!element.isNamed) {
- print(node.parent.runtimeType);
- print(node.parent?.parent.runtimeType);
- print(node.parent?.parent?.parent.runtimeType);
var root = node
.thisOrAncestorMatching((node) =>
node.parent is FunctionDeclaration ||
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 85ac443..9b4af58 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -125,46 +125,81 @@
status: needsEvaluation
since: in 2.15
CompileTimeErrorCode.CONCRETE_CLASS_HAS_ENUM_SUPERINTERFACE:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially offer a fix that would remove all of the types in the
+ class header that are or implement `Enum`, but I think it would be more
+ likely that the user needed to restructure the super interfaces so that
+ `Enum` wasn't included but the rest of the interfaces could be.
CompileTimeErrorCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER:
status: hasFix
CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_FIELD:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_GETTER:
- status: needsEvaluation
+ status: noFix
since: 2.15
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_METHOD:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_SETTER:
- status: needsEvaluation
+ status: noFix
since: 2.15
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_FIELD_AND_METHOD:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_GENERIC_INTERFACES:
status: needsEvaluation
CompileTimeErrorCode.CONFLICTING_METHOD_AND_FIELD:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_STATIC_AND_INSTANCE:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_CLASS:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_ENUM:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_EXTENSION:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_CLASS:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_ENUM:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_EXTENSION:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_MIXIN:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MIXIN:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ The fix is to rename one of the two, but we can't know what name to use.
CompileTimeErrorCode.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH:
status: needsEvaluation
CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH:
@@ -267,17 +302,22 @@
CompileTimeErrorCode.ENUM_CONSTANT_SAME_NAME_AS_ENCLOSING:
status: needsEvaluation
CompileTimeErrorCode.ENUM_CONSTANT_WITH_NON_CONST_CONSTRUCTOR:
- status: needsEvaluation
+ status: noFix
since: 2.17
CompileTimeErrorCode.ENUM_INSTANTIATED_TO_BOUNDS_IS_NOT_WELL_BOUNDED:
- status: needsEvaluation
+ status: noFix
since: 2.17
CompileTimeErrorCode.ENUM_MIXIN_WITH_INSTANCE_VARIABLE:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially offer a fix that would remove the mixin, but I think it
+ would be more likely that the user needed to restructure the mixin so that
+ part of it could be included here.
CompileTimeErrorCode.ENUM_WITH_ABSTRACT_MEMBER:
- status: needsEvaluation
+ status: needsFix
since: 2.17
+ issue: https://github.com/dart-lang/sdk/issues/48478
CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET:
status: needsEvaluation
CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP:
@@ -378,17 +418,28 @@
CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE:
status: hasFix
CompileTimeErrorCode.ILLEGAL_ENUM_VALUES_DECLARATION:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ While we could have a fix to remove the declaration, it seems more likely
+ that the user would want to rename it.
CompileTimeErrorCode.ILLEGAL_ENUM_VALUES_INHERITANCE:
- status: needsEvaluation
+ status: noFix
since: 2.17
CompileTimeErrorCode.ILLEGAL_LANGUAGE_VERSION_OVERRIDE:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially offer to remove the override, or to update it to the
+ lowest legal value, but it isn't clear that either of these is the right
+ fix without knowing why the language override was added.
CompileTimeErrorCode.ILLEGAL_NON_ABSTRACT_ENUM_INDEX:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially offer to remove the member, but the user probably needs
+ to think about what they were trying to do and it seems more likely that the
+ right fix is to rename the member.
CompileTimeErrorCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE:
status: hasFix
CompileTimeErrorCode.IMPLEMENTS_DEFERRED_CLASS:
@@ -484,7 +535,7 @@
CompileTimeErrorCode.INVALID_OVERRIDE:
status: hasFix
CompileTimeErrorCode.INVALID_REFERENCE_TO_GENERATIVE_ENUM_CONSTRUCTOR:
- status: needsEvaluation
+ status: noFix
since: 2.17
CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS:
status: needsEvaluation
@@ -617,8 +668,9 @@
CompileTimeErrorCode.NON_BOOL_OPERAND:
status: needsEvaluation
CompileTimeErrorCode.NON_CONST_GENERATIVE_ENUM_CONSTRUCTOR:
- status: needsEvaluation
+ status: needsFix
since: 2.17
+ issue: https://github.com/dart-lang/sdk/issues/48479
CompileTimeErrorCode.NON_CONST_MAP_AS_EXPRESSION_STATEMENT:
status: needsEvaluation
CompileTimeErrorCode.NON_CONSTANT_ANNOTATION_CONSTRUCTOR:
@@ -786,14 +838,19 @@
status: needsFix
since: 2.17
CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_NAMED:
- status: needsEvaluation
+ status: needsFix
since: 2.17
+ issue: https://github.com/dart-lang/sdk/issues/48480
CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_POSITIONAL:
status: needsFix
issue: https://github.com/dart-lang/sdk/issues/48359
CompileTimeErrorCode.SUPER_IN_ENUM_CONSTRUCTOR:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially offer a fix to remove the super invocation, but the
+ user really needs to think about what constructor they were trying to invoke
+ and why in order to really fix the issue.
CompileTimeErrorCode.SUPER_IN_EXTENSION:
status: needsEvaluation
CompileTimeErrorCode.SUPER_IN_INVALID_CONTEXT:
@@ -858,11 +915,13 @@
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTANT:
status: needsEvaluation
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED:
- status: needsEvaluation
+ status: needsFix
since: 2.17
+ issue: https://github.com/dart-lang/sdk/issues/48481
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED:
- status: needsEvaluation
+ status: needsFix
since: 2.17
+ issue: https://github.com/dart-lang/sdk/issues/48481
CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER:
status: hasFix
CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD:
@@ -922,8 +981,12 @@
CompileTimeErrorCode.USE_OF_VOID_RESULT:
status: needsEvaluation
CompileTimeErrorCode.VALUES_DECLARATION_IN_ENUM:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially offer to remove the member, but the user probably needs
+ to think about what they were trying to do and it seems more likely that the
+ right fix is to rename the member.
CompileTimeErrorCode.VARIABLE_TYPE_MISMATCH:
status: needsEvaluation
CompileTimeErrorCode.WRONG_EXPLICIT_TYPE_PARAMETER_VARIANCE_IN_SUPERINTERFACE:
@@ -942,8 +1005,12 @@
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR:
status: hasFix
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_ENUM:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We can't know which type arguments to add or remove. (We do have a fix to
+ remove all type arguments that would work here, it just isn't a very good
+ fix to suggest.)
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_EXTENSION:
status: hasFix
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_FUNCTION:
@@ -1855,10 +1922,17 @@
ParserErrorCode.DUPLICATED_MODIFIER:
status: needsEvaluation
ParserErrorCode.EMPTY_ENUM_BODY:
- status: needsEvaluation
+ status: noFix
+ notes: |-
+ We can't guess at the names or number of the enum constants that should be
+ added.
ParserErrorCode.ENUM_CONSTANT_WITH_TYPE_ARGUMENTS_WITHOUT_ARGUMENTS:
- status: needsEvaluation
+ status: noFix
since: 2.17
+ notes: |-
+ We could potentially add `()`, but we generally don't have fixes for parse
+ errors because we assume the user is still typing and will soon fix the
+ problem.
ParserErrorCode.ENUM_IN_CLASS:
status: needsEvaluation
ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND:
diff --git a/pkg/analyzer/lib/src/dart/constant/value.dart b/pkg/analyzer/lib/src/dart/constant/value.dart
index ef76c24..06b9e06 100644
--- a/pkg/analyzer/lib/src/dart/constant/value.dart
+++ b/pkg/analyzer/lib/src/dart/constant/value.dart
@@ -167,16 +167,14 @@
TypeSystemImpl typeSystem,
DartType type,
) {
- if (type.element!.library!.isDartCore) {
- if (type.isDartCoreBool) {
- return DartObjectImpl(typeSystem, type, BoolState.UNKNOWN_VALUE);
- } else if (type.isDartCoreDouble) {
- return DartObjectImpl(typeSystem, type, DoubleState.UNKNOWN_VALUE);
- } else if (type.isDartCoreInt) {
- return DartObjectImpl(typeSystem, type, IntState.UNKNOWN_VALUE);
- } else if (type.isDartCoreString) {
- return DartObjectImpl(typeSystem, type, StringState.UNKNOWN_VALUE);
- }
+ if (type.isDartCoreBool) {
+ return DartObjectImpl(typeSystem, type, BoolState.UNKNOWN_VALUE);
+ } else if (type.isDartCoreDouble) {
+ return DartObjectImpl(typeSystem, type, DoubleState.UNKNOWN_VALUE);
+ } else if (type.isDartCoreInt) {
+ return DartObjectImpl(typeSystem, type, IntState.UNKNOWN_VALUE);
+ } else if (type.isDartCoreString) {
+ return DartObjectImpl(typeSystem, type, StringState.UNKNOWN_VALUE);
}
return DartObjectImpl(typeSystem, type, GenericState.UNKNOWN_VALUE);
}
diff --git a/pkg/analyzer/test/generated/constant_test.dart b/pkg/analyzer/test/generated/constant_test.dart
index 9be808b..7e7f0ab 100644
--- a/pkg/analyzer/test/generated/constant_test.dart
+++ b/pkg/analyzer/test/generated/constant_test.dart
@@ -7,6 +7,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -49,6 +50,25 @@
await _assertValueInt(74 ^ 42, "74 ^ 42");
}
+ test_conditionalExpression_unknownCondition_dynamic() async {
+ await assertErrorsInCode('''
+const bool kIsWeb = identical(0, 0.0);
+const x = kIsWeb ? a : b;
+''', [
+ error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 58,
+ 1),
+ error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 58, 1),
+ error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 62, 1),
+ error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 62,
+ 1),
+ ]);
+
+ var x_result = findElement.topVar('x').evaluationResult;
+ assertDartObjectText(x_result.value, r'''
+dynamic <unknown>
+''');
+ }
+
test_constructorInvocation_fieldInitializer() async {
var result = await _getExpressionValue("const C(2)", context: '''
class C {
diff --git a/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart b/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
index 5514beb..8124576b 100644
--- a/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
+++ b/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
@@ -16,7 +16,14 @@
void write(DartObjectImpl? object, String indent) {
if (object != null) {
var type = object.type;
- if (type.isDartCoreDouble) {
+ if (object.isUnknown) {
+ sink.write(
+ type.getDisplayString(
+ withNullability: true,
+ ),
+ );
+ sink.writeln(' <unknown>');
+ } else if (type.isDartCoreDouble) {
sink.write('double ');
sink.writeln(object.toDoubleValue());
} else if (type.isDartCoreInt) {
diff --git a/pkg/dartdev/lib/src/analysis_server.dart b/pkg/dartdev/lib/src/analysis_server.dart
index c02c75c..c9fe68b0 100644
--- a/pkg/dartdev/lib/src/analysis_server.dart
+++ b/pkg/dartdev/lib/src/analysis_server.dart
@@ -84,7 +84,7 @@
Future<void> start({bool setAnalysisRoots = true}) async {
preAnalysisServerStart?.call(commandName, analysisRoots, argResults);
- final List<String> command = <String>[
+ final command = [
sdk.analysisServerSnapshot,
'--${Driver.SUPPRESS_ANALYTICS_FLAG}',
'--${Driver.CLIENT_ID}=dart-$commandName',
@@ -121,14 +121,15 @@
// protocol throws an error (INVALID_FILE_PATH_FORMAT) if there is a
// trailing slash.
//
- // The call to absolute.resolveSymbolicLinksSync() canonicalizes the path to
- // be passed to the analysis server.
- List<String> analysisRootPaths = analysisRoots.map((root) {
- return trimEnd(
- root.absolute.resolveSymbolicLinksSync(), path.context.separator)!;
- }).toList();
+ // The call to `absolute.resolveSymbolicLinksSync()` canonicalizes the path
+ // to be passed to the analysis server.
+ final analysisRootPaths = [
+ for (final root in analysisRoots)
+ trimEnd(
+ root.absolute.resolveSymbolicLinksSync(), path.context.separator),
+ ];
- onAnalyzing.listen((bool isAnalyzing) {
+ onAnalyzing.listen((isAnalyzing) {
if (isAnalyzing && _analysisFinished.isCompleted) {
// Start a new completer, to be completed when we receive the
// corresponding analysis complete event.
@@ -139,9 +140,9 @@
});
if (setAnalysisRoots) {
- await _sendCommand('analysis.setAnalysisRoots', params: <String, dynamic>{
+ await _sendCommand('analysis.setAnalysisRoots', params: {
'included': analysisRootPaths,
- 'excluded': <String>[]
+ 'excluded': [],
});
}
}
diff --git a/pkg/dartdev/lib/src/utils.dart b/pkg/dartdev/lib/src/utils.dart
index f4c5676..a80f7bd 100644
--- a/pkg/dartdev/lib/src/utils.dart
+++ b/pkg/dartdev/lib/src/utils.dart
@@ -31,8 +31,8 @@
}
/// String utility to trim some suffix from the end of a [String].
-String? trimEnd(String? s, String? suffix) {
- if (s != null && suffix != null && suffix.isNotEmpty && s.endsWith(suffix)) {
+String trimEnd(String s, String? suffix) {
+ if (suffix != null && suffix.isNotEmpty && s.endsWith(suffix)) {
return s.substring(0, s.length - suffix.length);
}
return s;
diff --git a/pkg/dartdev/test/commands/analyze_test.dart b/pkg/dartdev/test/commands/analyze_test.dart
index 04fc049..a75b441 100644
--- a/pkg/dartdev/test/commands/analyze_test.dart
+++ b/pkg/dartdev/test/commands/analyze_test.dart
@@ -172,15 +172,13 @@
}
void defineAnalyze() {
- TestProject? p;
+ late TestProject p;
- setUp(() => p = null);
-
- tearDown(() async => await p?.dispose());
+ tearDown(() async => await p.dispose());
test('--help', () async {
p = project();
- var result = await p!.run(['analyze', '--help']);
+ var result = await p.run(['analyze', '--help']);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -190,7 +188,7 @@
test('--help --verbose', () async {
p = project();
- var result = await p!.run(['analyze', '--help', '--verbose']);
+ var result = await p.run(['analyze', '--help', '--verbose']);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -206,8 +204,7 @@
test('folder and file', () async {
p = project(mainSrc: "int get foo => 'str';\n");
secondProject = project(mainSrc: "int get foo => 'str';\n");
- var result =
- await p!.run(['analyze', p!.dirPath, secondProject.mainPath]);
+ var result = await p.run(['analyze', p.dirPath, secondProject.mainPath]);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
@@ -220,7 +217,7 @@
test('two folders', () async {
p = project(mainSrc: "int get foo => 'str';\n");
secondProject = project(mainSrc: "int get foo => 'str';\n");
- var result = await p!.run(['analyze', p!.dirPath, secondProject.dirPath]);
+ var result = await p.run(['analyze', p.dirPath, secondProject.dirPath]);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
@@ -233,7 +230,7 @@
test('no such directory', () async {
p = project();
- var result = await p!.run(['analyze', '/no/such/dir1/']);
+ var result = await p.run(['analyze', '/no/such/dir1/']);
expect(result.exitCode, 64);
expect(result.stdout, isEmpty);
@@ -245,7 +242,7 @@
test('current working directory', () async {
p = project(mainSrc: 'int get foo => 1;\n');
- var result = await p!.run(['analyze'], workingDir: p!.dirPath);
+ var result = await p.run(['analyze'], workingDir: p.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -255,7 +252,7 @@
group('single directory', () {
test('no errors', () async {
p = project(mainSrc: 'int get foo => 1;\n');
- var result = await p!.run(['analyze', p!.dirPath]);
+ var result = await p.run(['analyze', p.dirPath]);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -264,7 +261,7 @@
test('one error', () async {
p = project(mainSrc: "int get foo => 'str';\n");
- var result = await p!.run(['analyze', p!.dirPath]);
+ var result = await p.run(['analyze', p.dirPath]);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
@@ -276,7 +273,7 @@
test('two errors', () async {
p = project(mainSrc: "int get foo => 'str';\nint get bar => 'str';\n");
- var result = await p!.run(['analyze', p!.dirPath]);
+ var result = await p.run(['analyze', p.dirPath]);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
@@ -287,7 +284,7 @@
group('single file', () {
test('no errors', () async {
p = project(mainSrc: 'int get foo => 1;\n');
- var result = await p!.run(['analyze', p!.mainPath]);
+ var result = await p.run(['analyze', p.mainPath]);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -296,7 +293,7 @@
test('one error', () async {
p = project(mainSrc: "int get foo => 'str';\n");
- var result = await p!.run(['analyze', p!.mainPath]);
+ var result = await p.run(['analyze', p.mainPath]);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
@@ -311,7 +308,7 @@
p = project(
mainSrc: _unusedImportCodeSnippet,
analysisOptions: _unusedImportAnalysisOptions);
- var result = await p!.run(['analyze', '--fatal-warnings', p!.dirPath]);
+ var result = await p.run(['analyze', '--fatal-warnings', p.dirPath]);
expect(result.exitCode, equals(2));
expect(result.stderr, isEmpty);
@@ -322,7 +319,7 @@
p = project(
mainSrc: _unusedImportCodeSnippet,
analysisOptions: _unusedImportAnalysisOptions);
- var result = await p!.run(['analyze', p!.dirPath]);
+ var result = await p.run(['analyze', p.dirPath]);
expect(result.exitCode, equals(2));
expect(result.stderr, isEmpty);
@@ -333,7 +330,7 @@
p = project(
mainSrc: _unusedImportCodeSnippet,
analysisOptions: _unusedImportAnalysisOptions);
- var result = await p!.run(['analyze', '--no-fatal-warnings', p!.dirPath]);
+ var result = await p.run(['analyze', '--no-fatal-warnings', p.dirPath]);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -342,7 +339,7 @@
test('info implicit no --fatal-infos', () async {
p = project(mainSrc: dartVersionFilePrefix2_9 + 'String foo() {}');
- var result = await p!.run(['analyze', p!.dirPath]);
+ var result = await p.run(['analyze', p.dirPath]);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
@@ -351,7 +348,7 @@
test('info --fatal-infos', () async {
p = project(mainSrc: dartVersionFilePrefix2_9 + 'String foo() {}');
- var result = await p!.run(['analyze', '--fatal-infos', p!.dirPath]);
+ var result = await p.run(['analyze', '--fatal-infos', p.dirPath]);
expect(result.exitCode, 1);
expect(result.stderr, isEmpty);
@@ -365,7 +362,7 @@
var one = 1;
return result;
}''');
- var result = await p!.run(['analyze', '--verbose', p!.dirPath]);
+ var result = await p.run(['analyze', '--verbose', p.dirPath]);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
@@ -387,7 +384,7 @@
void f() {
my_foo;
}''');
- p!.file('my_packages.json', '''
+ p.file('my_packages.json', '''
{
"configVersion": 2,
"packages": [
@@ -400,10 +397,10 @@
]
}
''');
- var result = await p!.run([
+ var result = await p.run([
'analyze',
- '--packages=${p!.findFile('my_packages.json')!.path}',
- p!.dirPath,
+ '--packages=${p.findFile('my_packages.json')!.path}',
+ p.dirPath,
]);
expect(result.exitCode, 0);
@@ -413,10 +410,10 @@
test('not existing', () async {
p = project();
- var result = await p!.run([
+ var result = await p.run([
'analyze',
'--packages=no.such.file',
- p!.dirPath,
+ p.dirPath,
]);
expect(result.exitCode, 64);
@@ -429,10 +426,10 @@
var cache = project(name: 'cache');
p = project(mainSrc: 'var v = 0;');
- var result = await p!.run([
+ var result = await p.run([
'analyze',
'--cache=${cache.dirPath}',
- p!.mainPath,
+ p.mainPath,
]);
expect(result.exitCode, 0);
diff --git a/pkg/dartdev/test/utils.dart b/pkg/dartdev/test/utils.dart
index 996b98b..e77686f 100644
--- a/pkg/dartdev/test/utils.dart
+++ b/pkg/dartdev/test/utils.dart
@@ -126,7 +126,7 @@
List<String> arguments, {
String? workingDir,
}) async {
- _process = await Process.start(
+ final process = await Process.start(
Platform.resolvedExecutable,
[
'--no-analytics',
@@ -137,12 +137,12 @@
if (logAnalytics) '_DARTDEV_LOG_ANALYTICS': 'true',
'PUB_CACHE': pubCachePath,
});
- final proc = _process!;
- final stdoutContents = proc.stdout.transform(utf8.decoder).join();
- final stderrContents = proc.stderr.transform(utf8.decoder).join();
- final code = await proc.exitCode;
+ _process = process;
+ final stdoutContents = process.stdout.transform(utf8.decoder).join();
+ final stderrContents = process.stderr.transform(utf8.decoder).join();
+ final code = await process.exitCode;
return ProcessResult(
- proc.pid,
+ process.pid,
code,
await stdoutContents,
await stderrContents,
diff --git a/pkg/dartdev/test/utils_test.dart b/pkg/dartdev/test/utils_test.dart
index 83f2567..811a0a5 100644
--- a/pkg/dartdev/test/utils_test.dart
+++ b/pkg/dartdev/test/utils_test.dart
@@ -37,10 +37,6 @@
});
group('trimEnd', () {
- test('null string', () {
- expect(trimEnd(null, 'suffix'), null);
- });
-
test('null suffix', () {
expect(trimEnd('string', null), 'string');
});
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index ed5ca6e..d7cfb1c 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -35,6 +35,7 @@
import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
import 'package:nnbd_migration/src/utilities/resolution_utils.dart';
import 'package:nnbd_migration/src/utilities/scoped_set.dart';
+import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart';
import 'package:nnbd_migration/src/utilities/where_or_null_transformer.dart';
import 'package:nnbd_migration/src/variables.dart';
@@ -236,6 +237,10 @@
/// equivalents.
final WhereOrNullTransformer _whereOrNullTransformer;
+ /// Helper that assists us in transforming calls to `Iterable.where` to
+ /// `Iterable.whereNotNull`.
+ final WhereNotNullTransformer _whereNotNullTransformer;
+
/// Deferred processing that should be performed once we have finished
/// evaluating the decorated type of a method invocation.
final Map<MethodInvocation, DecoratedType Function(DecoratedType?)>
@@ -252,7 +257,9 @@
{this.instrumentation})
: _inheritanceManager = InheritanceManager3(),
_whereOrNullTransformer =
- WhereOrNullTransformer(typeProvider, _typeSystem);
+ WhereOrNullTransformer(typeProvider, _typeSystem),
+ _whereNotNullTransformer =
+ WhereNotNullTransformer(typeProvider, _typeSystem);
/// The synthetic element we use as a stand-in for `this` when analyzing
/// extension methods.
@@ -2499,32 +2506,16 @@
sourceType = _makeNullableDynamicType(compoundOperatorInfo);
}
} else {
- var transformationInfo =
- _whereOrNullTransformer.tryTransformOrElseArgument(expression);
- if (transformationInfo != null) {
- // Don't build any edges for this argument; if necessary we'll transform
- // it rather than make things nullable. But do save the nullability of
- // the return value of the `orElse` method, so that we can later connect
- // it to the nullability of the value returned from the method
- // invocation.
- var extraNullability = sourceType.returnType!.node;
- _deferredMethodInvocationProcessing[
- transformationInfo.methodInvocation] = (methodInvocationType) {
- var newNode = NullabilityNode.forInferredType(
- NullabilityNodeTarget.text(
- 'return value from ${transformationInfo.originalName}'));
- var origin = IteratorMethodReturnOrigin(
- source, transformationInfo.methodInvocation);
- _graph.connect(methodInvocationType!.node, newNode, origin);
- _graph.connect(extraNullability, newNode, origin);
- return methodInvocationType.withNode(newNode);
- };
+ if (_tryTransformOrElse(expression, sourceType) ||
+ _tryTransformWhere(
+ expression, edgeOrigin, sourceType, destinationType!)) {
+ // Nothing further to do.
} else {
var hard = _shouldUseHardEdge(expression!,
isConditionallyExecuted: questionAssignNode != null);
_checkAssignment(edgeOrigin, FixReasonTarget.root,
source: sourceType,
- destination: destinationType!,
+ destination: destinationType,
hard: hard,
sourceIsFunctionLiteral: expression is FunctionExpression);
}
@@ -3465,6 +3456,57 @@
}
}
+ /// If [node] is an `orElse` argument to an iterable method that should be
+ /// transformed, performs the necessary assignment checks on the expression
+ /// type and returns `true` (this indicates to the caller that no further
+ /// assignment checks need to be performed); otherwise returns `false`.
+ bool _tryTransformOrElse(Expression? expression, DecoratedType sourceType) {
+ var transformationInfo =
+ _whereOrNullTransformer.tryTransformOrElseArgument(expression);
+ if (transformationInfo != null) {
+ // Don't build any edges for this argument; if necessary we'll transform
+ // it rather than make things nullable. But do save the nullability of
+ // the return value of the `orElse` method, so that we can later connect
+ // it to the nullability of the value returned from the method
+ // invocation.
+ var extraNullability = sourceType.returnType!.node;
+ _deferredMethodInvocationProcessing[transformationInfo.methodInvocation] =
+ (methodInvocationType) {
+ var newNode = NullabilityNode.forInferredType(
+ NullabilityNodeTarget.text(
+ 'return value from ${transformationInfo.originalName}'));
+ var origin = IteratorMethodReturnOrigin(
+ source, transformationInfo.methodInvocation);
+ _graph.connect(methodInvocationType!.node, newNode, origin);
+ _graph.connect(extraNullability, newNode, origin);
+ return methodInvocationType.withNode(newNode);
+ };
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /// If [node] is a call to `Iterable.where` that should be transformed,
+ /// performs the necessary assignment checks on the expression type and
+ /// returns `true` (this indicates to the caller that no further assignment
+ /// checks need to be performed); otherwise returns `false`.
+ bool _tryTransformWhere(Expression? expression, EdgeOrigin edgeOrigin,
+ DecoratedType sourceType, DecoratedType destinationType) {
+ var transformationInfo =
+ _whereNotNullTransformer.tryTransformMethodInvocation(expression);
+ if (transformationInfo != null) {
+ _checkAssignment(edgeOrigin, FixReasonTarget.root,
+ source: _whereNotNullTransformer.transformDecoratedInvocationType(
+ sourceType, _graph),
+ destination: destinationType,
+ hard: false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
Never _unimplemented(AstNode? node, String message) {
StringBuffer buffer = StringBuffer();
buffer.write(message);
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 8ebe8d5..ed266eb 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -38,6 +38,7 @@
import 'package:nnbd_migration/src/utilities/built_value_transformer.dart';
import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
import 'package:nnbd_migration/src/utilities/resolution_utils.dart';
+import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart';
import 'package:nnbd_migration/src/utilities/where_or_null_transformer.dart';
import 'package:nnbd_migration/src/variables.dart';
import 'package:pub_semver/pub_semver.dart';
@@ -125,10 +126,13 @@
/// equivalents.
final WhereOrNullTransformer _whereOrNullTransformer;
- /// Indicates whether an import of package:collection's `IterableExtension`
- /// will need to be added.
+ /// Helper that assists us in transforming calls to `Iterable.where` to
+ /// `Iterable.whereNotNull`.
+ final WhereNotNullTransformer _whereNotNullTransformer;
+
+ /// The set of extensions that need to be imported from package:collection.
@visibleForTesting
- bool needsIterableExtension = false;
+ final Set<String> neededCollectionPackageExtensions = {};
/// Map of additional package dependencies that will be required by the
/// migrated code. Keys are package names; values indicate the minimum
@@ -179,7 +183,9 @@
this._neededPackages)
: typeProvider = _typeSystem.typeProvider,
_whereOrNullTransformer =
- WhereOrNullTransformer(_typeSystem.typeProvider, _typeSystem) {
+ WhereOrNullTransformer(_typeSystem.typeProvider, _typeSystem),
+ _whereNotNullTransformer =
+ WhereNotNullTransformer(_typeSystem.typeProvider, _typeSystem) {
migrationResolutionHooks._fixBuilder = this;
assert(_typeSystem.isNonNullableByDefault);
assert((typeProvider as TypeProviderImpl).isNonNullableByDefault);
@@ -603,6 +609,15 @@
return change.resultType;
}
+ /// Ensures that the migrated file will contain an import of the extension
+ /// called [extensionName] from `package:collection/collection.dart`, and that
+ /// the pubspec will be appropriately updated (if necessary).
+ void _addCollectionPackageExtension(String extensionName) {
+ _fixBuilder!.neededCollectionPackageExtensions.add(extensionName);
+ _fixBuilder!._neededPackages['collection'] =
+ Version.parse('1.15.0-nullsafety.4');
+ }
+
DartType _addNullCheck(Expression node, DartType type,
{AtomicEditInfo? info, HintComment? hint}) {
var change = _createNullCheckChange(node, type, hint: hint);
@@ -730,32 +745,13 @@
var ancestor = _findNullabilityContextAncestor(node);
context ??= _contextTypes[ancestor] ?? DynamicTypeImpl.instance;
if (!_isSubtypeOrCoercible(type, context)) {
- var transformationInfo =
- _fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node);
- if (transformationInfo != null) {
- // We can fix this by dropping the node and changing the method call.
- _fixBuilder!.needsIterableExtension = true;
- _fixBuilder!._neededPackages['collection'] =
- Version.parse('1.15.0-nullsafety.4');
- var info = AtomicEditInfo(
- NullabilityFixDescription.changeMethodName(
- transformationInfo.originalName,
- transformationInfo.replacementName),
- {});
- (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName)
- as NodeChangeForMethodName)
- .replaceWith(transformationInfo.replacementName, info);
- (_fixBuilder!._getChange(
- transformationInfo.methodInvocation.argumentList)
- as NodeChangeForArgumentList)
- .dropArgument(transformationInfo.orElseArgument, info);
- _deferredMethodInvocationProcessing[
- transformationInfo.methodInvocation] =
- (methodInvocationType) => _fixBuilder!._typeSystem
- .makeNullable(methodInvocationType as TypeImpl);
- return type;
- }
- return _addCastOrNullCheck(node, type, context);
+ return _tryTransformOrElse(node, type) ??
+ _tryTransformWhere(node, type) ??
+ _addCastOrNullCheck(node, type, context);
+ } else {
+ // Even if the type is a subtype of its context, we still want to
+ // transform `.where`.
+ type = _tryTransformWhere(node, type) ?? type;
}
if (!_fixBuilder!._typeSystem.isNullable(type)) return type;
if (_needsNullCheckDueToStructure(ancestor)) {
@@ -847,6 +843,63 @@
.toList();
}
+ /// If [node] is an `orElse` argument to an iterable method that should be
+ /// transformed, transforms it and returns [type] (this indicates to the
+ /// caller that no further transformations to this expression need to be
+ /// considered); otherwise returns `null`.
+ DartType? _tryTransformOrElse(Expression node, DartType type) {
+ var transformationInfo =
+ _fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node);
+ if (transformationInfo != null) {
+ // We can fix this by dropping the node and changing the method call.
+ _addCollectionPackageExtension('IterableExtension');
+ var info = AtomicEditInfo(
+ NullabilityFixDescription.changeMethodName(
+ transformationInfo.originalName,
+ transformationInfo.replacementName),
+ {});
+ (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName)
+ as NodeChangeForMethodName)
+ .replaceWith(transformationInfo.replacementName, info);
+ (_fixBuilder!._getChange(transformationInfo.methodInvocation.argumentList)
+ as NodeChangeForArgumentList)
+ .dropArgument(transformationInfo.orElseArgument, info);
+ _deferredMethodInvocationProcessing[transformationInfo.methodInvocation] =
+ (methodInvocationType) => _fixBuilder!._typeSystem
+ .makeNullable(methodInvocationType as TypeImpl);
+ return type;
+ }
+ return null;
+ }
+
+ /// If [node] is a call to `Iterable.where` that should be transformed,
+ /// transforms it and returns the type of the transformed method call (this
+ /// indicates to the caller that no further transformations to this expression
+ /// need to be considered); otherwise returns `null`.
+ DartType? _tryTransformWhere(Expression node, DartType type) {
+ var transformationInfo = _fixBuilder!._whereNotNullTransformer
+ .tryTransformMethodInvocation(node);
+ if (transformationInfo != null) {
+ // We can fix this by dropping the method call's argument and changing the
+ // call.
+ _addCollectionPackageExtension('IterableNullableExtension');
+ var info = AtomicEditInfo(
+ NullabilityFixDescription.changeMethodName(
+ transformationInfo.originalName,
+ transformationInfo.replacementName),
+ {});
+ (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName)
+ as NodeChangeForMethodName)
+ .replaceWith(transformationInfo.replacementName, info);
+ (_fixBuilder!._getChange(transformationInfo.methodInvocation.argumentList)
+ as NodeChangeForArgumentList)
+ .dropArgument(transformationInfo.argument, info);
+ return _fixBuilder!._whereNotNullTransformer
+ .transformPostMigrationInvocationType(type);
+ }
+ return null;
+ }
+
/// Runs the computation in [compute]. If an exception occurs and
/// [_fixBuilder.listener] is non-null, the exception is reported to the
/// listener and [fallback] is called to produce a result. Otherwise the
@@ -1012,19 +1065,25 @@
(_fixBuilder._getChange(node) as NodeChangeForCompilationUnit)
.removeLanguageVersionComment = true;
}
- if (_fixBuilder.needsIterableExtension) {
+ if (_fixBuilder.neededCollectionPackageExtensions.isNotEmpty) {
var packageCollectionImport =
_findImportDirective(node, 'package:collection/collection.dart');
if (packageCollectionImport != null) {
for (var combinator in packageCollectionImport.combinators) {
if (combinator is ShowCombinator) {
- _ensureShows(combinator, 'IterableExtension');
+ for (var extensionName
+ in _fixBuilder.neededCollectionPackageExtensions) {
+ _ensureShows(combinator, extensionName);
+ }
}
}
} else {
- (_fixBuilder._getChange(node) as NodeChangeForCompilationUnit)
- .addImport(
- 'package:collection/collection.dart', 'IterableExtension');
+ var change =
+ _fixBuilder._getChange(node) as NodeChangeForCompilationUnit;
+ for (var extensionName
+ in _fixBuilder.neededCollectionPackageExtensions) {
+ change.addImport('package:collection/collection.dart', extensionName);
+ }
}
}
super.visitCompilationUnit(node);
diff --git a/pkg/nnbd_migration/lib/src/utilities/where_not_null_transformer.dart b/pkg/nnbd_migration/lib/src/utilities/where_not_null_transformer.dart
new file mode 100644
index 0000000..cc27031
--- /dev/null
+++ b/pkg/nnbd_migration/lib/src/utilities/where_not_null_transformer.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2022, 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/ast/token.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/type_provider.dart';
+import 'package:analyzer/dart/element/type_system.dart';
+import 'package:analyzer/src/dart/element/type.dart';
+import 'package:nnbd_migration/src/decorated_type.dart';
+import 'package:nnbd_migration/src/nullability_node.dart';
+
+/// Information about a method call that we might want to transform into a call
+/// to `whereNotNull` counterpart. See [WhereNotNullTransformer] for more
+/// information.
+class WhereNotNullTransformationInfo {
+ /// AST node of the method invocation.
+ final MethodInvocation methodInvocation;
+
+ /// AST node of the argument of the method invocation.
+ final Expression argument;
+
+ /// Original name of the method being called, prior to transformation.
+ final String originalName;
+
+ WhereNotNullTransformationInfo(
+ this.methodInvocation, this.argument, this.originalName);
+
+ /// New method to call, after transformation.
+ String get replacementName => 'whereNotNull';
+}
+
+/// Methods to assist in transforming calls to the `Iterable` method `where`
+/// into calls to the `package:collection` method `whereNotNull`, where
+/// possible.
+///
+/// An example of the kind of code that can be transformed is:
+///
+/// Iterable<int> f(List<int/*?*/> x) => x.where((y) => y != null);
+///
+/// We transform this into:
+///
+/// Iterable<int> f(List<int?> x) => x.whereNotNull();
+///
+/// Without this transformation, the migrated result would have been:
+///
+/// Iterable<int?> f(List<int/*?*/> x) => x.where((y) => y != null);
+///
+/// Which would have placed an otherwise unnecessary requirement on callers to
+/// handle `null` values in the resulting iterable.
+class WhereNotNullTransformer {
+ final TypeProvider _typeProvider;
+
+ final TypeSystem _typeSystem;
+
+ WhereNotNullTransformer(this._typeProvider, this._typeSystem);
+
+ /// Transforms the [DecoratedType] of an invocation of `.where` to the
+ /// [DecoratedType] of the corresponding invocation of `.whereNotNull` that
+ /// will replace it.
+ ///
+ /// The transformation is that the type argument to `Iterable` is made
+ /// non-nullable, so that nullability doesn't unnecessarily propagate to other
+ /// parts of the code.
+ DecoratedType transformDecoratedInvocationType(
+ DecoratedType decoratedType, NullabilityGraph graph) {
+ var type = decoratedType.type;
+ var typeArguments = decoratedType.typeArguments;
+ if (type is InterfaceType &&
+ type.element == _typeProvider.iterableElement &&
+ typeArguments.length == 1) {
+ return DecoratedType(type, decoratedType.node,
+ typeArguments: [typeArguments.single?.withNode(graph.never)]);
+ }
+ return decoratedType;
+ }
+
+ /// Transforms the post-migration type of an invocation of `.where` to the
+ /// type of the corresponding invocation of `.whereNotNull` that will replace
+ /// it.
+ ///
+ /// The transformation is that the type argument to `Iterable` is made
+ /// non-nullable, so that we don't try to introduce any unnecessary null
+ /// checks or type casts in other parts of the code.
+ DartType transformPostMigrationInvocationType(DartType type) {
+ if (type is InterfaceType &&
+ type.element == _typeProvider.iterableElement) {
+ var typeArguments = type.typeArguments;
+ if (typeArguments.length == 1) {
+ return InterfaceTypeImpl(
+ element: type.element,
+ typeArguments: [_typeSystem.promoteToNonNull(typeArguments.single)],
+ nullabilitySuffix: type.nullabilitySuffix);
+ }
+ }
+ return type;
+ }
+
+ /// If [node] is a call that can be transformed, returns information about the
+ /// transformable call; otherwise returns `null`.
+ WhereNotNullTransformationInfo? tryTransformMethodInvocation(AstNode? node) {
+ if (node is MethodInvocation) {
+ if (!_isTransformableMethod(node.methodName.staticElement)) return null;
+ var arguments = node.argumentList.arguments;
+ if (arguments.length != 1) return null;
+ var argument = arguments[0];
+ if (!_isClosureCheckingNotNull(argument)) return null;
+ return WhereNotNullTransformationInfo(
+ node, argument, node.methodName.name);
+ }
+ return null;
+ }
+
+ /// Checks whether [expression] is of the form `(x) => x != null`.
+ bool _isClosureCheckingNotNull(Expression expression) {
+ if (expression is! FunctionExpression) return false;
+ if (expression.typeParameters != null) return false;
+ var parameters = expression.parameters!.parameters;
+ if (parameters.length != 1) return false;
+ var parameter = parameters[0];
+ if (parameter.isNamed) return false;
+ var body = expression.body;
+ if (body is! ExpressionFunctionBody) return false;
+ var returnedExpression = body.expression;
+ if (returnedExpression is! BinaryExpression) return false;
+ if (returnedExpression.operator.type != TokenType.BANG_EQ) return false;
+ var lhs = returnedExpression.leftOperand;
+ if (lhs is! SimpleIdentifier) return false;
+ if (lhs.staticElement != parameter.declaredElement) return false;
+ if (returnedExpression.rightOperand is! NullLiteral) return false;
+ return true;
+ }
+
+ /// Determines if [element] is a declaration of `.where` for which calls can
+ /// be transformed.
+ bool _isTransformableMethod(Element? element) {
+ if (element is MethodElement) {
+ if (element.isStatic) return false;
+ if (element.name != 'where') return false;
+ var enclosingElement = element.declaration.enclosingElement;
+ if (enclosingElement is ClassElement) {
+ // If the class is `Iterable` or a subtype of it, we consider the user
+ // to be calling a transformable method.
+ return _typeSystem.isSubtypeOf(
+ enclosingElement.thisType, _typeProvider.iterableDynamicType);
+ }
+ }
+ return false;
+ }
+}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index c567c61..e2799e8 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -9425,6 +9425,58 @@
''';
await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
}
+
+ Future<void> test_whereNotNull() async {
+ var content = '''
+Iterable<String> f(Iterable<String/*?*/> it) => it.where((s) => s != null);
+''';
+ var expected = '''
+import 'package:collection/collection.dart' show IterableNullableExtension;
+
+Iterable<String> f(Iterable<String?> it) => it.whereNotNull();
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_whereNotNull_and_firstWhereOrNull() async {
+ var content = '''
+Iterable<String> f(Iterable<String/*?*/> it) => it.where((s) => s != null);
+int g(Iterable<int> it) => it.firstWhere((i) => i != 0, orElse: () => null);
+''';
+ var expected = '''
+import 'package:collection/collection.dart' show IterableExtension, IterableNullableExtension;
+
+Iterable<String> f(Iterable<String?> it) => it.whereNotNull();
+int? g(Iterable<int> it) => it.firstWhereOrNull((i) => i != 0);
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_whereNotNull_complexType() async {
+ var content = '''
+Iterable<Map<String, int>> f(Iterable<Map<String/*?*/, int>/*?*/> it)
+ => it.where((m) => m != null);
+''';
+ var expected = '''
+import 'package:collection/collection.dart' show IterableNullableExtension;
+
+Iterable<Map<String?, int>> f(Iterable<Map<String?, int>?> it)
+ => it.whereNotNull();
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_whereNotNull_noContext() async {
+ var content = '''
+f(Iterable<String/*?*/> it) => it.where((s) => s != null);
+''';
+ var expected = '''
+import 'package:collection/collection.dart' show IterableNullableExtension;
+
+f(Iterable<String?> it) => it.whereNotNull();
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
}
@reflectiveTest
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index 76c2bca..322ab2a 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -1433,7 +1433,7 @@
// Behavior of the null literal doesn't matter because it's being dropped.
findNode.nullLiteral('null'): anything
});
- expect(fixBuilder.needsIterableExtension, true);
+ expect(fixBuilder.neededCollectionPackageExtensions, {'IterableExtension'});
}
Future<void> test_functionExpressionInvocation_dynamic() async {
@@ -3812,13 +3812,29 @@
});
}
+ Future<void> test_where_transform() async {
+ await analyze('''
+Iterable<int> _f(Iterable<int/*?*/> x) => x.where((e) => e != null);
+''');
+ var methodInvocation = findNode.methodInvocation('where');
+ var functionExpression = findNode.functionExpression('(e) => e != null');
+ var fixBuilder =
+ visitSubexpression(methodInvocation, 'Iterable<int>', changes: {
+ methodInvocation.methodName: isMethodNameChange('whereNotNull'),
+ methodInvocation.argumentList:
+ isDropArgument({functionExpression: anything}),
+ });
+ expect(fixBuilder.neededCollectionPackageExtensions,
+ {'IterableNullableExtension'});
+ }
+
void visitAll(
{Map<AstNode, Matcher> changes = const <Expression, Matcher>{},
Map<AstNode, Set<Problem>> problems = const <AstNode, Set<Problem>>{},
bool injectNeedsIterableExtension = false}) {
var fixBuilder = _createFixBuilder(testUnit!);
if (injectNeedsIterableExtension) {
- fixBuilder.needsIterableExtension = true;
+ fixBuilder.neededCollectionPackageExtensions.add('IterableExtension');
}
fixBuilder.visitAll();
expect(scopedChanges(fixBuilder, testUnit), changes);
@@ -3930,16 +3946,16 @@
.having((c) => c.expressionChangeInfos.single,
'expressionChangeInfos.single', infoMatcher);
- TypeMatcher<T> havingNullCheckWithInfo(dynamic matcher) =>
- havingExpressionChange(TypeMatcher<NullCheckChange>(), matcher);
-
- TypeMatcher<T> havingNoValidMigrationWithInfo(dynamic matcher) =>
- havingExpressionChange(TypeMatcher<NoValidMigrationChange>(), matcher);
-
TypeMatcher<T> havingIndroduceAsWithInfo(
dynamic typeStringMatcher, dynamic infoMatcher) =>
havingExpressionChange(
TypeMatcher<IntroduceAsChange>().having(
(c) => c.type.toString(), 'type (string)', typeStringMatcher),
infoMatcher);
+
+ TypeMatcher<T> havingNoValidMigrationWithInfo(dynamic matcher) =>
+ havingExpressionChange(TypeMatcher<NoValidMigrationChange>(), matcher);
+
+ TypeMatcher<T> havingNullCheckWithInfo(dynamic matcher) =>
+ havingExpressionChange(TypeMatcher<NullCheckChange>(), matcher);
}
diff --git a/pkg/nnbd_migration/test/utilities/test_all.dart b/pkg/nnbd_migration/test/utilities/test_all.dart
index dd93187..9c04ec1e 100644
--- a/pkg/nnbd_migration/test/utilities/test_all.dart
+++ b/pkg/nnbd_migration/test/utilities/test_all.dart
@@ -9,6 +9,8 @@
import 'source_edit_diff_formatter_test.dart'
as source_edit_diff_formatter_test;
import 'subprocess_launcher_test.dart' as subprocess_launcher_test;
+import 'where_not_null_transformer_test.dart'
+ as where_not_null_transformer_test;
import 'where_or_null_transformer_test.dart' as where_or_null_transformer_test;
main() {
@@ -17,6 +19,7 @@
scoped_set_test.main();
source_edit_diff_formatter_test.main();
subprocess_launcher_test.main();
+ where_not_null_transformer_test.main();
where_or_null_transformer_test.main();
});
}
diff --git a/pkg/nnbd_migration/test/utilities/where_not_null_transformer_test.dart b/pkg/nnbd_migration/test/utilities/where_not_null_transformer_test.dart
new file mode 100644
index 0000000..a4e9f47
--- /dev/null
+++ b/pkg/nnbd_migration/test/utilities/where_not_null_transformer_test.dart
@@ -0,0 +1,212 @@
+// Copyright (c) 2022, 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/type_provider.dart';
+import 'package:analyzer/dart/element/type_system.dart';
+import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../abstract_single_unit.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(WhereNotNullTransformerTest);
+ });
+}
+
+@reflectiveTest
+class WhereNotNullTransformerTest extends AbstractSingleUnitTest {
+ late WhereNotNullTransformer transformer;
+
+ TypeProvider get typeProvider => testAnalysisResult.typeProvider;
+
+ TypeSystem get typeSystem => testAnalysisResult.typeSystem;
+
+ Future<void> analyze(String code) async {
+ await resolveTestUnit(code);
+ transformer = WhereNotNullTransformer(typeProvider, typeSystem);
+ }
+
+ Future<void> test_match() async {
+ await analyze('''
+f(List<int> x) => x.where((i) => i != null);
+''');
+ var methodInvocation = findNode.methodInvocation('.where');
+ var transformationInfo =
+ transformer.tryTransformMethodInvocation(methodInvocation)!;
+ expect(transformationInfo, isNotNull);
+ expect(transformationInfo.methodInvocation, same(methodInvocation));
+ expect(transformationInfo.argument,
+ same(findNode.functionExpression('(i) => i != null')));
+ expect(transformationInfo.originalName, 'where');
+ expect(transformationInfo.replacementName, 'whereNotNull');
+ }
+
+ Future<void> test_match_extended() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != null);
+''');
+ var methodInvocation = findNode.methodInvocation('.where');
+ var transformationInfo =
+ transformer.tryTransformMethodInvocation(methodInvocation)!;
+ expect(transformationInfo, isNotNull);
+ expect(transformationInfo.methodInvocation, same(methodInvocation));
+ expect(transformationInfo.argument,
+ same(findNode.functionExpression('(i) => i != null')));
+ expect(transformationInfo.originalName, 'where');
+ expect(transformationInfo.replacementName, 'whereNotNull');
+ }
+
+ Future<void> test_match_returns_subtype() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ List<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != null);
+''');
+ var methodInvocation = findNode.methodInvocation('.where');
+ var transformationInfo =
+ transformer.tryTransformMethodInvocation(methodInvocation)!;
+ expect(transformationInfo, isNotNull);
+ expect(transformationInfo.methodInvocation, same(methodInvocation));
+ expect(transformationInfo.argument,
+ same(findNode.functionExpression('(i) => i != null')));
+ expect(transformationInfo.originalName, 'where');
+ expect(transformationInfo.replacementName, 'whereNotNull');
+ }
+
+ Future<void> test_mismatch_closure_block_typed() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) { return i != null; });
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_closure_lhs_not_identifier() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => 2*i != null);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_closure_lhs_wrong_element() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => c != null);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_closure_non_binary_expression() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => true);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_closure_wrong_operator() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i == null);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_extension_method() async {
+ await analyze('''
+extension on String {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(String s) => s.where((i) => i != null);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_misnamed_method() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> fooBar(bool test(int element)) => null;
+}
+f(C c) => c.fooBar((i) => i != null);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.fooBar')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_not_a_subtype_of_iterable() async {
+ await analyze('''
+abstract class C {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != null);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_rhs_not_null() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != 0);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+
+ Future<void> test_mismatch_too_many_arguments() async {
+ await analyze('''
+abstract class C implements Iterable<int> {
+ Iterable<int> where(bool test(int element), {int x}) => null;
+}
+f(C c) => c.where((i) => i != null, x: 0);
+''');
+ expect(
+ transformer
+ .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+ isNull);
+ }
+}
diff --git a/tools/VERSION b/tools/VERSION
index c429a9a..2401c9d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 155
+PRERELEASE 156
PRERELEASE_PATCH 0
\ No newline at end of file