Move hasMultipleVersions to Description (#4576)

diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index fa56f21..f5f8d22 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -714,7 +714,7 @@
       final description = pubspecDescription(
         ref.withConstraint(
           constraint ??
-              (ref.source is HostedSource
+              (ref.description.hasMultipleVersions
                   ? VersionConstraint.compatibleWith(resultId.version)
                   : VersionConstraint.any),
         ),
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index 5626dde..9d4ba45 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -17,7 +17,6 @@
 import '../pubspec.dart';
 import '../pubspec_utils.dart';
 import '../solver.dart';
-import '../source/hosted.dart';
 import '../utils.dart';
 
 /// Handles the `upgrade` pub command.
@@ -248,11 +247,11 @@
     // Mapping from original to changed value.
     var changes = <Package, Map<PackageRange, PackageRange>>{};
     for (final package in entrypoint.workspaceRoot.transitiveWorkspace) {
-      final declaredHostedDependencies = [
+      final declaredUpgradableDependencies = [
         ...package.dependencies.values,
         ...package.devDependencies.values,
-      ].where((dep) => dep.source is HostedSource);
-      for (final dep in declaredHostedDependencies) {
+      ].where((dep) => dep.description.hasMultipleVersions);
+      for (final dep in declaredUpgradableDependencies) {
         final resolvedPackage = resolvedPackages[dep.name]!;
         if (!toUpgrade.contains(dep.name)) {
           // If we're not trying to upgrade this package, or it wasn't in the
diff --git a/lib/src/package_name.dart b/lib/src/package_name.dart
index 647561e..7ec1c28 100644
--- a/lib/src/package_name.dart
+++ b/lib/src/package_name.dart
@@ -174,7 +174,7 @@
   bool get _showVersionConstraint {
     if (isRoot) return false;
     if (!constraint.isAny) return true;
-    return description.source.hasMultipleVersions;
+    return description.hasMultipleVersions;
   }
 
   /// Returns a copy of `this` with the same semantics, but with a `^`-style
diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart
index 165da9b..0d0f119 100644
--- a/lib/src/solver/version_solver.dart
+++ b/lib/src/solver/version_solver.dart
@@ -586,7 +586,10 @@
     // can't be downgraded.
     if (_type == SolveType.downgrade) {
       final locked = _lockFile.packages[package];
-      if (locked != null && !locked.source.hasMultipleVersions) return locked;
+      if (locked != null &&
+          !locked.description.description.hasMultipleVersions) {
+        return locked;
+      }
     }
 
     if (_unlock.isEmpty || _unlock.contains(package)) return null;
diff --git a/lib/src/source.dart b/lib/src/source.dart
index 824ea5b..37727e5 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -51,12 +51,6 @@
   /// all sources.
   String get name;
 
-  /// Whether this source can choose between multiple versions of the same
-  /// package during version solving.
-  ///
-  /// Defaults to `false`.
-  bool get hasMultipleVersions => false;
-
   /// Parses a [PackageRef] from a name and a user-provided [description].
   ///
   /// When a [Pubspec] is parsed, it reads in the description for each
@@ -190,6 +184,11 @@
 /// with a version constraint.
 abstract class Description {
   Source get source;
+
+  /// Whether the source can choose between multiple versions of this
+  /// package during version solving.
+  bool get hasMultipleVersions;
+
   Object? serializeForPubspec({
     required String? containingDir,
     required LanguageVersion languageVersion,
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index d5236b6..8c7ff04 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -892,6 +892,9 @@
     }
     return p.prettyUri(url);
   }
+
+  @override
+  bool get hasMultipleVersions => false;
 }
 
 class ResolvedGitDescription extends ResolvedDescription {
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 925309a..9b73cb3 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -123,8 +123,6 @@
 
   @override
   final name = 'hosted';
-  @override
-  final hasMultipleVersions = true;
 
   static String pubDevUrl = 'https://pub.dev';
   static String pubDartlangUrl = 'https://pub.dartlang.org';
@@ -1818,6 +1816,9 @@
 
   @override
   HostedSource get source => HostedSource.instance;
+
+  @override
+  bool get hasMultipleVersions => true;
 }
 
 class ResolvedHostedDescription extends ResolvedDescription {
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index f395ba0..7859e5e 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -313,6 +313,9 @@
 
   @override
   int get hashCode => _canonicalizedPath.hashCode;
+
+  @override
+  bool get hasMultipleVersions => false;
 }
 
 class ResolvedPathDescription extends ResolvedDescription {
diff --git a/lib/src/source/root.dart b/lib/src/source/root.dart
index 9a178d0..44342bc 100644
--- a/lib/src/source/root.dart
+++ b/lib/src/source/root.dart
@@ -119,4 +119,7 @@
 
   @override
   int get hashCode => 'root'.hashCode;
+
+  @override
+  bool get hasMultipleVersions => false;
 }
diff --git a/lib/src/source/sdk.dart b/lib/src/source/sdk.dart
index 536c29b..8469ef6 100644
--- a/lib/src/source/sdk.dart
+++ b/lib/src/source/sdk.dart
@@ -188,6 +188,9 @@
   bool operator ==(Object other) {
     return other is SdkDescription && other.sdk == sdk;
   }
+
+  @override
+  bool get hasMultipleVersions => false;
 }
 
 class ResolvedSdkDescription extends ResolvedDescription {
diff --git a/lib/src/source/unknown.dart b/lib/src/source/unknown.dart
index a151e9e..8774898 100644
--- a/lib/src/source/unknown.dart
+++ b/lib/src/source/unknown.dart
@@ -111,6 +111,9 @@
 
   @override
   int get hashCode => Object.hash(source.name, json.encode(description));
+
+  @override
+  bool get hasMultipleVersions => false;
 }
 
 class ResolvedUnknownDescription extends ResolvedDescription {