`dart pub outdated` - Better message when no resolution exists. (#2840)

diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index 9659d32..7c7806c 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -130,10 +130,19 @@
 
     List<PackageId> upgradablePackages;
     List<PackageId> resolvablePackages;
+    bool hasUpgradableResolution;
+    bool hasResolvableResolution;
 
     await log.spinner('Resolving', () async {
-      upgradablePackages = await _tryResolve(upgradablePubspec, cache);
-      resolvablePackages = await _tryResolve(resolvablePubspec, cache);
+      final upgradablePackagesResult =
+          await _tryResolve(upgradablePubspec, cache);
+      hasUpgradableResolution = upgradablePackagesResult != null;
+      upgradablePackages = upgradablePackagesResult ?? [];
+
+      final resolvablePackagesResult =
+          await _tryResolve(resolvablePubspec, cache);
+      hasResolvableResolution = resolvablePackagesResult != null;
+      resolvablePackages = resolvablePackagesResult ?? [];
     }, condition: _shouldShowSpinner);
 
     // This list will be empty if there is no lock file.
@@ -259,6 +268,8 @@
           (c) => c.source is! SdkSource,
         ),
         showTransitiveDependencies: showTransitiveDependencies,
+        hasUpgradableResolution: hasUpgradableResolution,
+        hasResolvableResolution: hasResolvableResolution,
       );
     }
   }
@@ -371,18 +382,16 @@
   }
 }
 
-/// Try to solve [pubspec] return [PackageId]s in the resolution or `[]`.
+/// Try to solve [pubspec] return [PackageId]s in the resolution or `null` if no
+/// resolution was found.
 Future<List<PackageId>> _tryResolve(Pubspec pubspec, SystemCache cache) async {
   final solveResult = await tryResolveVersions(
     SolveType.UPGRADE,
     cache,
     Package.inMemory(pubspec),
   );
-  if (solveResult == null) {
-    return [];
-  }
 
-  return solveResult.packages;
+  return solveResult?.packages;
 }
 
 Future<void> _outputJson(
@@ -431,6 +440,8 @@
   @required bool hasDirectDependencies,
   @required bool hasDevDependencies,
   @required bool showTransitiveDependencies,
+  @required bool hasUpgradableResolution,
+  @required bool hasResolvableResolution,
 }) async {
   final explanation = mode.explanation;
   if (explanation != null) {
@@ -540,7 +551,9 @@
               hasKind(_DependencyKind.dev)(row)))
       .length;
 
-  if (lockFileExists) {
+  if (!hasUpgradableResolution || !hasResolvableResolution) {
+    log.message(mode.noResolutionText);
+  } else if (lockFileExists) {
     if (upgradable != 0) {
       if (upgradable == 1) {
         log.message('\n1 upgradable dependency is locked (in pubspec.lock) to '
@@ -553,21 +566,17 @@
             'To update these dependencies, use `dart pub upgrade`.');
       }
     }
+
+    if (notAtResolvable == 0 && upgradable == 0 && rows.isNotEmpty) {
+      log.message(
+          "You are already using the newest resolvable versions listed in the 'Resolvable' column.\n"
+          "Newer versions, listed in 'Latest', may not be mutually compatible.");
+    }
   } else {
     log.message('\nNo pubspec.lock found. There are no Current versions.\n'
         'Run `pub get` to create a pubspec.lock with versions matching your '
         'pubspec.yaml.');
   }
-
-  if (lockFileExists &&
-      notAtResolvable == 0 &&
-      upgradable == 0 &&
-      rows.isNotEmpty) {
-    log.message(
-        "You are already using the newest resolvable versions listed in the 'Resolvable' column.\n"
-        "Newer versions, listed in 'Latest', may not be mutually compatible.");
-  }
-
   if (notAtResolvable != 0) {
     if (notAtResolvable == 1) {
       log.message('\n1 dependency is constrained to a '
@@ -591,6 +600,7 @@
   String get explanation;
   String get foundNoBadText;
   String get allGood;
+  String get noResolutionText;
   String get upgradeConstrained;
 
   Future<Pubspec> resolvablePubspec(Pubspec pubspec);
@@ -610,6 +620,10 @@
   String get allGood => 'all up-to-date.';
 
   @override
+  String get noResolutionText =>
+      '''No resolution was found. Try running `dart pub upgrade --dry-run` to explore why.''';
+
+  @override
   String get upgradeConstrained =>
       'edit pubspec.yaml, or run `dart pub upgrade --major-versions`';
 
@@ -686,6 +700,10 @@
   String get allGood => 'all support null safety.';
 
   @override
+  String get noResolutionText =>
+      '''No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.''';
+
+  @override
   String get upgradeConstrained =>
       'edit pubspec.yaml, or run `dart pub upgrade --null-safety`';
 
diff --git a/test/outdated/goldens/dependency_overrides_no_solution.txt b/test/outdated/goldens/dependency_overrides_no_solution.txt
index 4e04886..9efeb58 100644
--- a/test/outdated/goldens/dependency_overrides_no_solution.txt
+++ b/test/outdated/goldens/dependency_overrides_no_solution.txt
@@ -109,8 +109,7 @@
 direct dependencies:
 bar           *1.0.0 (overridden)  -           -           2.0.0   
 foo           *1.0.0 (overridden)  -           -           2.0.0   
-You are already using the newest resolvable versions listed in the 'Resolvable' column.
-Newer versions, listed in 'Latest', may not be mutually compatible.
+No resolution was found. Try running `dart pub upgrade --dry-run` to explore why.
 
 $ pub outdated --no-color --mode=null-safety
 Showing dependencies that are currently not opted in to null-safety.
diff --git a/test/outdated/goldens/null_safety_no_resolution.txt b/test/outdated/goldens/null_safety_no_resolution.txt
new file mode 100644
index 0000000..89fb0e2
--- /dev/null
+++ b/test/outdated/goldens/null_safety_no_resolution.txt
@@ -0,0 +1,132 @@
+$ pub outdated --json
+{
+  "packages": []
+}
+
+$ pub outdated --no-color
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+$ pub outdated --no-color --no-transitive
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+$ pub outdated --no-color --up-to-date
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable  Resolvable  Latest  
+
+direct dependencies:
+bar           1.0.0    1.0.0       1.0.0       1.0.0   
+foo           1.0.0    1.0.0       1.0.0       1.0.0   
+You are already using the newest resolvable versions listed in the 'Resolvable' column.
+Newer versions, listed in 'Latest', may not be mutually compatible.
+
+$ pub outdated --no-color --prereleases
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Package Name  Current  Upgradable  Resolvable  Latest              
+
+direct dependencies:
+bar           *1.0.0   *1.0.0      *1.0.0      2.0.0-nullsafety.0  
+foo           *1.0.0   *1.0.0      *1.0.0      2.0.0-nullsafety.0  
+You are already using the newest resolvable versions listed in the 'Resolvable' column.
+Newer versions, listed in 'Latest', may not be mutually compatible.
+
+$ pub outdated --no-color --no-dev-dependencies
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+$ pub outdated --no-color --no-dependency-overrides
+Showing outdated packages.
+[*] indicates versions that are not the latest available.
+
+Found no outdated packages
+
+$ pub outdated --no-color --mode=null-safety
+Showing dependencies that are currently not opted in to null-safety.
+[✗] indicates versions without null safety support.
+[✓] indicates versions opting in to null safety.
+
+Package Name  Current  Upgradable  Resolvable  Latest               
+
+direct dependencies:
+bar           ✗1.0.0   ✗1.0.0      -           ✓2.0.0-nullsafety.0  
+foo           ✗1.0.0   ✗1.0.0      -           ✓2.0.0-nullsafety.0  
+No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.
+
+$ pub outdated --no-color --mode=null-safety --transitive
+Showing dependencies that are currently not opted in to null-safety.
+[✗] indicates versions without null safety support.
+[✓] indicates versions opting in to null safety.
+
+Package Name  Current  Upgradable  Resolvable  Latest               
+
+direct dependencies:
+bar           ✗1.0.0   ✗1.0.0      -           ✓2.0.0-nullsafety.0  
+foo           ✗1.0.0   ✗1.0.0      -           ✓2.0.0-nullsafety.0  
+No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.
+
+$ pub outdated --no-color --mode=null-safety --no-prereleases
+Showing dependencies that are currently not opted in to null-safety.
+[✗] indicates versions without null safety support.
+[✓] indicates versions opting in to null safety.
+
+Package Name  Current  Upgradable  Resolvable  Latest  
+
+direct dependencies:
+bar           ✗1.0.0   ✗1.0.0      -           ✗1.0.0  
+foo           ✗1.0.0   ✗1.0.0      -           ✗1.0.0  
+No resolution was found. Try running `dart pub upgrade --null-safety --dry-run` to explore why.
+
+$ pub outdated --json --mode=null-safety
+{
+  "packages": [
+    {
+      "package": "bar",
+      "current": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "upgradable": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "resolvable": null,
+      "latest": {
+        "version": "2.0.0-nullsafety.0",
+        "nullSafety": true
+      }
+    },
+    {
+      "package": "foo",
+      "current": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "upgradable": {
+        "version": "1.0.0",
+        "nullSafety": false
+      },
+      "resolvable": null,
+      "latest": {
+        "version": "2.0.0-nullsafety.0",
+        "nullSafety": true
+      }
+    }
+  ]
+}
+
+$ pub outdated --json --no-dev-dependencies
+{
+  "packages": []
+}
+
diff --git a/test/outdated/outdated_test.dart b/test/outdated/outdated_test.dart
index d110142..7d2b1cd 100644
--- a/test/outdated/outdated_test.dart
+++ b/test/outdated/outdated_test.dart
@@ -258,6 +258,43 @@
         environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
   });
 
+  test('null-safety no resolution', () async {
+    await servePackages((builder) => builder
+      ..serve('foo', '1.0.0', pubspec: {
+        'environment': {'sdk': '>=2.9.0 < 3.0.0'}
+      })
+      ..serve('foo', '2.0.0-nullsafety.0', deps: {
+        'bar': '^1.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'}
+      })
+      ..serve('bar', '1.0.0', pubspec: {
+        'environment': {'sdk': '>=2.9.0 < 3.0.0'}
+      })
+      ..serve('bar', '2.0.0-nullsafety.0', deps: {
+        'foo': '^1.0.0'
+      }, pubspec: {
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'}
+      }));
+
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'app',
+        'version': '1.0.0',
+        'dependencies': {
+          'foo': '^1.0.0',
+          'bar': '^1.0.0',
+        },
+        'environment': {'sdk': '>=2.12.0 < 3.0.0'},
+      }),
+    ]).create();
+
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
+
+    await variations('null_safety_no_resolution',
+        environment: {'_PUB_TEST_SDK_VERSION': '2.13.0'});
+  });
+
   test('overridden dependencies', () async {
     ensureGit();
     await servePackages(