Make >=2.12 <3.0.0 compatible with dart 3 (#3572)

diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 36f8047..2683cdf 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -20,7 +20,6 @@
 import 'exceptions.dart';
 import 'executable.dart';
 import 'io.dart';
-import 'language_version.dart';
 import 'lock_file.dart';
 import 'log.dart' as log;
 import 'package.dart';
@@ -281,7 +280,8 @@
       await lockFile.packageConfigFile(
         cache,
         entrypoint: entrypointName,
-        entrypointSdkConstraint: root.pubspec.sdkConstraints[sdk.identifier],
+        entrypointSdkConstraint:
+            root.pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint,
         relativeFrom: isGlobal ? null : root.dir,
       ),
     );
@@ -816,9 +816,7 @@
       try {
         // Load `pubspec.yaml` and extract language version to compare with the
         // language version from `package_config.json`.
-        final languageVersion = LanguageVersion.fromSdkConstraint(
-          cache.load(id).pubspec.sdkConstraints[sdk.identifier],
-        );
+        final languageVersion = cache.load(id).pubspec.languageVersion;
         if (pkg.languageVersion != languageVersion) {
           final relativePubspecPath = p.join(
             cache.getDirectory(id, relativeFrom: '.'),
@@ -874,7 +872,7 @@
   ///
   /// We don't allow unknown sdks.
   void _checkSdkConstraint(Pubspec pubspec) {
-    final dartSdkConstraint = pubspec.sdkConstraints['dart'];
+    final dartSdkConstraint = pubspec.dartSdkConstraint.effectiveConstraint;
     if (dartSdkConstraint is! VersionRange || dartSdkConstraint.min == null) {
       // Suggest version range '>=2.10.0 <3.0.0', we avoid using:
       // [CompatibleWithVersionRange] because some pub versions don't support
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index bb97023..d8cdef6 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -362,7 +362,9 @@
         dataError('${log.bold(name)} ${entrypoint.root.version} requires '
             'unknown SDK "$name".');
       } else if (sdkName == 'dart') {
-        if (constraint.allows((sdk as DartSdk).version)) return;
+        if (constraint.effectiveConstraint.allows((sdk as DartSdk).version)) {
+          return;
+        }
         dataError("${log.bold(name)} ${entrypoint.root.version} doesn't "
             'support Dart ${sdk.version}.');
       } else {
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart
index f133172..6c66224 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -306,12 +306,11 @@
         rootUri = p.toUri(rootPath);
       }
       final pubspec = await cache.describe(id);
-      final sdkConstraint = pubspec.sdkConstraints[sdk.identifier];
       entries.add(PackageConfigEntry(
         name: name,
         rootUri: rootUri,
         packageUri: p.toUri('lib/'),
-        languageVersion: LanguageVersion.fromSdkConstraint(sdkConstraint),
+        languageVersion: pubspec.languageVersion,
       ));
     }
 
diff --git a/lib/src/null_safety_analysis.dart b/lib/src/null_safety_analysis.dart
index fe89b55..284e845 100644
--- a/lib/src/null_safety_analysis.dart
+++ b/lib/src/null_safety_analysis.dart
@@ -91,7 +91,7 @@
           }
         },
         sources: _systemCache.sources,
-        sdkConstraints: {'dart': rootPubspec.sdkConstraints['dart']!}));
+        sdkConstraints: {'dart': rootPubspec.dartSdkConstraint}));
 
     final rootLanguageVersion = rootPubspec.languageVersion;
     if (!rootLanguageVersion.supportsNullSafety) {
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 6e1d566..69b5557 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -120,6 +120,9 @@
 
   Map<String, PackageRange>? _devDependencies;
 
+  /// The Dart sdk version this is parsed against.
+  final Version _dartSdkVersion;
+
   /// The dependency constraints that this package overrides when it is the
   /// root package.
   ///
@@ -163,13 +166,13 @@
 
   Map<String, PackageRange>? _dependencyOverrides;
 
-  /// A map from SDK identifiers to constraints on those SDK versions.
-  Map<String, VersionConstraint> get sdkConstraints {
-    _ensureEnvironment();
-    return _sdkConstraints!;
-  }
+  SdkConstraint get dartSdkConstraint => sdkConstraints['dart']!;
 
-  Map<String, VersionConstraint>? _sdkConstraints;
+  /// A map from SDK identifiers to constraints on those SDK versions.
+  late final Map<String, SdkConstraint> sdkConstraints =
+      _givenSdkConstraints ?? UnmodifiableMapView(_parseEnvironment(fields));
+
+  final Map<String, SdkConstraint>? _givenSdkConstraints;
 
   /// Whether or not to apply the [_defaultUpperBoundsSdkConstraint] to this
   /// pubspec.
@@ -177,41 +180,8 @@
 
   /// Whether or not the SDK version was overridden from <2.0.0 to
   /// <2.0.0-dev.infinity.
-  bool get dartSdkWasOverridden => _dartSdkWasOverridden;
-  bool _dartSdkWasOverridden = false;
-
-  /// The original Dart SDK constraint as written in the pubspec.
-  ///
-  /// If [dartSdkWasOverridden] is `false`, this will be identical to
-  /// `sdkConstraints["dart"]`.
-  VersionConstraint get originalDartSdkConstraint {
-    _ensureEnvironment();
-    return _originalDartSdkConstraint ?? sdkConstraints['dart']!;
-  }
-
-  VersionConstraint? _originalDartSdkConstraint;
-
-  /// Ensures that the top-level "environment" field has been parsed and
-  /// [_sdkConstraints] is set accordingly.
-  void _ensureEnvironment() {
-    if (_sdkConstraints != null) return;
-
-    var sdkConstraints = _parseEnvironment(fields);
-    var parsedDartSdkConstraint = sdkConstraints['dart'];
-
-    if (parsedDartSdkConstraint is VersionRange &&
-        _shouldEnableCurrentSdk(parsedDartSdkConstraint)) {
-      _originalDartSdkConstraint = parsedDartSdkConstraint;
-      _dartSdkWasOverridden = true;
-      sdkConstraints['dart'] = VersionRange(
-          min: parsedDartSdkConstraint.min,
-          includeMin: parsedDartSdkConstraint.includeMin,
-          max: sdk.version,
-          includeMax: true);
-    }
-
-    _sdkConstraints = UnmodifiableMapView(sdkConstraints);
-  }
+  bool get dartSdkWasOverridden => _dartSdkWasOverriddenToAllowPrerelease;
+  bool _dartSdkWasOverriddenToAllowPrerelease = false;
 
   /// Whether or not we should override [sdkConstraint] to be <= the user's
   /// current SDK version.
@@ -227,12 +197,12 @@
   ///     patch versions as the user's current SDK.
   bool _shouldEnableCurrentSdk(VersionRange sdkConstraint) {
     if (!_allowPreReleaseSdk) return false;
-    if (!sdk.version.isPreRelease) return false;
+    if (!_dartSdkVersion.isPreRelease) return false;
     if (sdkConstraint.includeMax) return false;
     var minSdkConstraint = sdkConstraint.min;
     if (minSdkConstraint != null &&
         minSdkConstraint.isPreRelease &&
-        equalsIgnoringPreRelease(sdkConstraint.min!, sdk.version)) {
+        equalsIgnoringPreRelease(sdkConstraint.min!, _dartSdkVersion)) {
       return false;
     }
     var maxSdkConstraint = sdkConstraint.max;
@@ -241,54 +211,121 @@
         !maxSdkConstraint.isFirstPreRelease) {
       return false;
     }
-    return equalsIgnoringPreRelease(maxSdkConstraint, sdk.version);
+    return equalsIgnoringPreRelease(maxSdkConstraint, _dartSdkVersion);
+  }
+
+  SdkConstraint _interpretDartSdkConstraint(
+    VersionConstraint originalConstraint, {
+    required VersionConstraint? defaultUpperBoundConstraint,
+  }) {
+    VersionConstraint constraint = originalConstraint;
+    if (constraint is VersionRange && _shouldEnableCurrentSdk(constraint)) {
+      _dartSdkWasOverriddenToAllowPrerelease = true;
+      constraint = VersionRange(
+          min: constraint.min,
+          includeMin: constraint.includeMin,
+          max: _dartSdkVersion,
+          includeMax: true);
+    }
+    if (defaultUpperBoundConstraint != null &&
+        constraint is VersionRange &&
+        constraint.max == null &&
+        defaultUpperBoundConstraint.allowsAny(constraint)) {
+      constraint = VersionConstraint.intersection(
+          [constraint, defaultUpperBoundConstraint]);
+    }
+    // If a package is null safe it should also be compatible with dart 3.
+    // Therefore we rewrite a null-safety enabled constraint with the upper
+    // bound <3.0.0 to be have upper bound <4.0.0
+    if (constraint is VersionRange &&
+        LanguageVersion.fromSdkConstraint(constraint) >=
+            LanguageVersion.firstVersionWithNullSafety &&
+        // <3.0.0 is parsed into a max of 3.0.0-0, so that is what we look for
+        // here.
+        constraint.max == Version(3, 0, 0).firstPreRelease &&
+        constraint.includeMax == false) {
+      constraint = VersionRange(
+        min: constraint.min,
+        includeMin: constraint.includeMin,
+        // We don't have to use .firstPreRelease as the constructor will do that
+        // if needed.
+        max: Version(4, 0, 0),
+      );
+    }
+    return SdkConstraint(constraint, originalConstraint: originalConstraint);
+  }
+
+  // Flutter constraints get special treatment, as Flutter won't be using
+  // semantic versioning to mark breaking releases. We simply ignore upper
+  // bounds.
+  SdkConstraint _interpretFlutterSdkConstraint(VersionConstraint constraint) {
+    if (constraint is VersionRange) {
+      return SdkConstraint(
+        VersionRange(min: constraint.min, includeMin: constraint.includeMin),
+        originalConstraint: constraint,
+      );
+    }
+    return SdkConstraint(constraint);
   }
 
   /// Parses the "environment" field in [parent] and returns a map from SDK
   /// identifiers to constraints on those SDKs.
-  Map<String, VersionConstraint> _parseEnvironment(YamlMap parent) {
+  Map<String, SdkConstraint> _parseEnvironment(YamlMap parent) {
     var yaml = parent['environment'];
+    final VersionConstraint originalDartSdkConstraint;
     if (yaml == null) {
-      return {
-        'dart': _includeDefaultSdkConstraint
-            ? _defaultUpperBoundSdkConstraint
-            : VersionConstraint.any
-      };
-    }
-
-    if (yaml is! YamlMap) {
+      originalDartSdkConstraint = _includeDefaultSdkConstraint
+          ? _defaultUpperBoundSdkConstraint
+          : VersionConstraint.any;
+    } else if (yaml is! YamlMap) {
       _error('"environment" field must be a map.',
           parent.nodes['environment']!.span);
+    } else {
+      originalDartSdkConstraint = _parseVersionConstraint(
+        yaml.nodes['sdk'],
+        _packageName,
+        _FileType.pubspec,
+      );
     }
 
     var constraints = {
-      'dart': _parseVersionConstraint(
-          yaml.nodes['sdk'], _packageName, _FileType.pubspec,
-          defaultUpperBoundConstraint: _includeDefaultSdkConstraint
-              ? _defaultUpperBoundSdkConstraint
-              : null)
+      'dart': _interpretDartSdkConstraint(
+        originalDartSdkConstraint,
+        defaultUpperBoundConstraint: _includeDefaultSdkConstraint
+            ? _defaultUpperBoundSdkConstraint
+            : null,
+      )
     };
-    yaml.nodes.forEach((name, constraint) {
-      if (name.value is! String) {
-        _error('SDK names must be strings.', name.span);
-      } else if (name.value == 'dart') {
-        _error('Use "sdk" to for Dart SDK constraints.', name.span);
-      }
-      if (name.value == 'sdk') return;
 
-      constraints[name.value as String] =
-          _parseVersionConstraint(constraint, _packageName, _FileType.pubspec,
-              // Flutter constraints get special treatment, as Flutter won't be
-              // using semantic versioning to mark breaking releases.
-              ignoreUpperBound: name.value == 'flutter');
-    });
+    if (yaml is YamlMap) {
+      yaml.nodes.forEach((nameNode, constraintNode) {
+        final name = nameNode.value;
+        if (name is! String) {
+          _error('SDK names must be strings.', nameNode.span);
+        } else if (name == 'dart') {
+          _error('Use "sdk" to for Dart SDK constraints.', nameNode.span);
+        }
+        if (name == 'sdk') return;
 
+        final constraint = _parseVersionConstraint(
+          constraintNode,
+          _packageName,
+          _FileType.pubspec,
+        );
+        constraints[name] = name == 'flutter'
+            ? _interpretFlutterSdkConstraint(constraint)
+            : SdkConstraint(constraint);
+      });
+    }
     return constraints;
   }
 
   /// The language version implied by the sdk constraint.
-  LanguageVersion get languageVersion =>
-      LanguageVersion.fromSdkConstraint(originalDartSdkConstraint);
+  LanguageVersion get languageVersion {
+    return LanguageVersion.fromSdkConstraint(
+      dartSdkConstraint.originalConstraint,
+    );
+  }
 
   /// Loads the pubspec for a package located in [packageDir].
   ///
@@ -332,7 +369,8 @@
     Iterable<PackageRange>? dependencyOverrides,
     Map? fields,
     SourceRegistry? sources,
-    Map<String, VersionConstraint>? sdkConstraints,
+    Map<String, SdkConstraint>? sdkConstraints,
+    Version? dartSdkVersion,
   })  : _dependencies = dependencies == null
             ? null
             : Map.fromIterable(dependencies, key: (range) => range.name),
@@ -342,12 +380,13 @@
         _dependencyOverrides = dependencyOverrides == null
             ? null
             : Map.fromIterable(dependencyOverrides, key: (range) => range.name),
-        _sdkConstraints = sdkConstraints ??
-            UnmodifiableMapView({'dart': VersionConstraint.any}),
+        _givenSdkConstraints = sdkConstraints ??
+            UnmodifiableMapView({'dart': SdkConstraint(VersionConstraint.any)}),
         _includeDefaultSdkConstraint = false,
         _sources = sources ??
             ((String? name) => throw StateError('No source registry given')),
         _overridesFileFields = null,
+        _dartSdkVersion = dartSdkVersion ?? sdk.version,
         super(
           fields == null ? YamlMap() : YamlMap.wrap(fields),
           name: name,
@@ -361,10 +400,17 @@
   /// field, this will throw a [PubspecError].
   ///
   /// [location] is the location from which this pubspec was loaded.
-  Pubspec.fromMap(Map fields, this._sources,
-      {YamlMap? overridesFields, String? expectedName, Uri? location})
-      : _overridesFileFields = overridesFields,
+  Pubspec.fromMap(
+    Map fields,
+    this._sources, {
+    YamlMap? overridesFields,
+    String? expectedName,
+    Uri? location,
+    Version? dartSdkVersion,
+  })  : _overridesFileFields = overridesFields,
         _includeDefaultSdkConstraint = true,
+        _givenSdkConstraints = null,
+        _dartSdkVersion = dartSdkVersion ?? sdk.version,
         super(fields is YamlMap
             ? fields
             : YamlMap.wrap(fields, sourceUrl: location)) {
@@ -390,6 +436,7 @@
     Uri? location,
     String? overridesFileContents,
     Uri? overridesLocation,
+    Version? dartSdkVersion,
   }) {
     late final YamlMap pubspecMap;
     YamlMap? overridesFileMap;
@@ -403,10 +450,14 @@
       throw SourceSpanApplicationException(error.message, error.span);
     }
 
-    return Pubspec.fromMap(pubspecMap, sources,
-        overridesFields: overridesFileMap,
-        expectedName: expectedName,
-        location: location);
+    return Pubspec.fromMap(
+      pubspecMap,
+      sources,
+      overridesFields: overridesFileMap,
+      expectedName: expectedName,
+      location: location,
+      dartSdkVersion: dartSdkVersion,
+    );
   }
 
   /// Ensures that [node] is a mapping.
@@ -445,7 +496,7 @@
     collectError(() => publishTo);
     collectError(() => executables);
     collectError(() => falseSecrets);
-    collectError(_ensureEnvironment);
+    collectError(() => sdkConstraints);
     return errors;
   }
 }
@@ -558,17 +609,13 @@
 
 /// Parses [node] to a [VersionConstraint].
 ///
-/// If or [defaultUpperBoundConstraint] is specified then it will be set as
-/// the max constraint if the original constraint doesn't have an upper
-/// bound and it is compatible with [defaultUpperBoundConstraint].
-///
-/// If [ignoreUpperBound] the max constraint is ignored.
+/// If or [defaultUpperBoundConstraint] is specified then it will be set as the
+/// max constraint if the original constraint doesn't have an upper bound and it
+/// is compatible with [defaultUpperBoundConstraint].
 VersionConstraint _parseVersionConstraint(
-    YamlNode? node, String? packageName, _FileType fileType,
-    {VersionConstraint? defaultUpperBoundConstraint,
-    bool ignoreUpperBound = false}) {
+    YamlNode? node, String? packageName, _FileType fileType) {
   if (node?.value == null) {
-    return defaultUpperBoundConstraint ?? VersionConstraint.any;
+    return VersionConstraint.any;
   }
   if (node!.value is! String) {
     _error('A version constraint must be a string.', node.span);
@@ -576,17 +623,6 @@
 
   return _wrapFormatException('version constraint', node.span, () {
     var constraint = VersionConstraint.parse(node.value);
-    if (defaultUpperBoundConstraint != null &&
-        constraint is VersionRange &&
-        constraint.max == null &&
-        defaultUpperBoundConstraint.allowsAny(constraint)) {
-      constraint = VersionConstraint.intersection(
-          [constraint, defaultUpperBoundConstraint]);
-    }
-    if (ignoreUpperBound && constraint is VersionRange) {
-      return VersionRange(
-          min: constraint.min, includeMin: constraint.includeMin);
-    }
     return constraint;
   }, packageName, fileType);
 }
@@ -643,3 +679,40 @@
       return 'pubspec override';
   }
 }
+
+/// There are special rules or interpreting SDK constraints, we take care to
+/// save the original constraint as found in pubspec.yaml.
+class SdkConstraint {
+  /// The constraint as written in the pubspec.yaml.
+  final VersionConstraint originalConstraint;
+
+  /// The constraint as interpreted by pub.
+  final VersionConstraint effectiveConstraint;
+
+  SdkConstraint(this.effectiveConstraint,
+      {VersionConstraint? originalConstraint})
+      : originalConstraint = originalConstraint ?? effectiveConstraint;
+
+  /// The language version of a constraint is determined from how it is written.
+  LanguageVersion get languageVersion =>
+      LanguageVersion.fromSdkConstraint(originalConstraint);
+
+  // We currently don't call this anywhere - so this is only for debugging
+  // purposes.
+  @override
+  String toString() {
+    if (effectiveConstraint != originalConstraint) {
+      return '$originalConstraint (interpreted as $effectiveConstraint)';
+    }
+    return effectiveConstraint.toString();
+  }
+
+  @override
+  operator ==(other) =>
+      other is SdkConstraint &&
+      other.effectiveConstraint == effectiveConstraint &&
+      other.originalConstraint == originalConstraint;
+
+  @override
+  int get hashCode => Object.hash(effectiveConstraint, originalConstraint);
+}
diff --git a/lib/src/solver/package_lister.dart b/lib/src/solver/package_lister.dart
index 1597e73..f3b63b6 100644
--- a/lib/src/solver/package_lister.dart
+++ b/lib/src/solver/package_lister.dart
@@ -218,8 +218,11 @@
       for (var sdk in sdks.values) {
         if (!_matchesSdkConstraint(pubspec, sdk)) {
           return [
-            Incompatibility([Term(depender, true)],
-                SdkCause(pubspec.sdkConstraints[sdk.identifier], sdk))
+            Incompatibility(
+                [Term(depender, true)],
+                SdkCause(
+                    pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint,
+                    sdk))
           ];
         }
       }
@@ -321,12 +324,13 @@
         alwaysIncludeMaxPreRelease: true);
     _knownInvalidVersions = incompatibleVersions.union(_knownInvalidVersions);
 
-    var sdkConstraint = await foldAsync(
+    var sdkConstraint = await foldAsync<VersionConstraint, PackageId>(
         slice(versions, bounds.first, bounds.last + 1), VersionConstraint.empty,
-        (dynamic previous, dynamic version) async {
+        (previous, version) async {
       var pubspec = await _describeSafe(version);
       return previous.union(
-          pubspec.sdkConstraints[sdk.identifier] ?? VersionConstraint.any);
+          pubspec.sdkConstraints[sdk.identifier]?.effectiveConstraint ??
+              VersionConstraint.any);
     });
 
     return Incompatibility(
@@ -428,6 +432,7 @@
     var constraint = pubspec.sdkConstraints[sdk.identifier];
     if (constraint == null) return true;
 
-    return sdk.isAvailable && constraint.allows(sdk.version!);
+    return sdk.isAvailable &&
+        constraint.effectiveConstraint.allows(sdk.version!);
   }
 }
diff --git a/lib/src/solver/result.dart b/lib/src/solver/result.dart
index 7d57c36..df3aa4a 100644
--- a/lib/src/solver/result.dart
+++ b/lib/src/solver/result.dart
@@ -63,7 +63,7 @@
     var sdkConstraints = <String, VersionConstraint>{};
     for (var pubspec in nonOverrides) {
       pubspec.sdkConstraints.forEach((identifier, constraint) {
-        sdkConstraints[identifier] = constraint
+        sdkConstraints[identifier] = constraint.effectiveConstraint
             .intersect(sdkConstraints[identifier] ?? VersionConstraint.any);
       });
     }
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index 82de012..cbf930f 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -75,7 +75,7 @@
   void validateSdkConstraint(Version firstSdkVersion, String message) {
     // If the SDK constraint disallowed all versions before [firstSdkVersion],
     // no error is necessary.
-    if (entrypoint.root.pubspec.originalDartSdkConstraint
+    if (entrypoint.root.pubspec.dartSdkConstraint.originalConstraint
         .intersect(VersionRange(max: firstSdkVersion))
         .isEmpty) {
       return;
@@ -96,7 +96,8 @@
             ? firstSdkVersion.nextPatch
             : firstSdkVersion.nextBreaking);
 
-    var newSdkConstraint = entrypoint.root.pubspec.originalDartSdkConstraint
+    var newSdkConstraint = entrypoint
+        .root.pubspec.dartSdkConstraint.originalConstraint
         .intersect(allowedSdks);
     if (newSdkConstraint.isEmpty) newSdkConstraint = allowedSdks;
 
diff --git a/lib/src/validator/flutter_plugin_format.dart b/lib/src/validator/flutter_plugin_format.dart
index a56f7df..00160ca 100644
--- a/lib/src/validator/flutter_plugin_format.dart
+++ b/lib/src/validator/flutter_plugin_format.dart
@@ -46,7 +46,7 @@
     final flutterConstraint = pubspec.sdkConstraints['flutter'];
     if (usesNewPluginFormat &&
         (flutterConstraint == null ||
-            flutterConstraint.allowsAny(VersionRange(
+            flutterConstraint.effectiveConstraint.allowsAny(VersionRange(
               min: Version.parse('0.0.0'),
               max: Version.parse('1.10.0'),
               includeMin: true,
diff --git a/test/dart3_sdk_constraint_hack_test.dart b/test/dart3_sdk_constraint_hack_test.dart
new file mode 100644
index 0000000..7df3ee8
--- /dev/null
+++ b/test/dart3_sdk_constraint_hack_test.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, 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:test/test.dart';
+
+import 'descriptor.dart' as d;
+import 'test_pub.dart';
+
+void main() {
+  test('The bound of ">=2.11.0 <3.0.0" is not modified', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.11.0 <3.0.0'}
+      }),
+    ]).create();
+
+    await pubGet(
+      error: contains(
+          'Because myapp requires SDK version >=2.11.0 <3.0.0, version solving failed'),
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+  });
+  test('The bound of ">=2.12.0 <3.1.0" is not modified', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.12.0 <3.1.0'}
+      }),
+    ]).create();
+
+    await pubGet(
+      error: contains(
+          'Because myapp requires SDK version >=2.12.0 <3.1.0, version solving failed'),
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+  });
+
+  test('The bound of ">=2.11.0 <2.999.0" is not modified', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.11.0 <2.999.0'}
+      }),
+    ]).create();
+
+    await pubGet(
+      error: contains(
+          'Because myapp requires SDK version >=2.11.0 <2.999.0, version solving failed'),
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+  });
+
+  test('The bound of ">=2.11.0 <3.0.0-0.0" is not modified', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.11.0 <3.0.0-0.0'}
+      }),
+    ]).create();
+
+    await pubGet(
+      error: contains(
+          'Because myapp requires SDK version >=2.11.0 <3.0.0-0.0, version solving failed'),
+      environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'},
+    );
+  });
+
+  test('The bound of ">=2.12.0 <3.0.0" is modified', () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.12.0 <3.0.0'}
+      }),
+    ]).create();
+
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'});
+  });
+
+  test('The bound of ">=2.12.0 <3.0.0-0" is modified', () async {
+    // For the upper bound <3.0.0 is treated as <3.0.0-0, so they both have
+    // the rewrite applied.
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.12.0 <3.0.0-0'}
+      }),
+    ]).create();
+
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'});
+  });
+
+  test(
+      'The bound of ">=2.12.0 <3.0.0" is not compatible with prereleases of dart 4',
+      () async {
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'myapp',
+        'environment': {'sdk': '>=2.12.0 <3.0.0'}
+      }),
+    ]).create();
+
+    await pubGet(
+      environment: {'_PUB_TEST_SDK_VERSION': '4.0.0-alpha'},
+      error: contains(
+        'Because myapp requires SDK version >=2.12.0 <4.0.0, version solving failed.',
+      ),
+    );
+  });
+}
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index 1a9ac48..23079c5 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -6,7 +6,6 @@
 
 import 'package:pub/src/exceptions.dart';
 import 'package:pub/src/pubspec.dart';
-import 'package:pub/src/sdk.dart';
 import 'package:pub/src/source/hosted.dart';
 import 'package:pub/src/system_cache.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -495,36 +494,23 @@
     });
 
     group('environment', () {
-      /// Checking for the default SDK constraint based on the current SDK.
-      void expectDefaultSdkConstraint(Pubspec pubspec) {
-        var sdkVersionString = sdk.version.toString();
-        if (sdkVersionString.startsWith('2.0.0') && sdk.version.isPreRelease) {
-          expect(
-              pubspec.sdkConstraints,
-              containsPair(
-                  'dart',
-                  VersionConstraint.parse(
-                      '${pubspec.sdkConstraints["dart"]} <=$sdkVersionString')));
-        } else {
-          expect(
-              pubspec.sdkConstraints,
-              containsPair(
-                  'dart',
-                  VersionConstraint.parse(
-                      "${pubspec.sdkConstraints["dart"]} <2.0.0")));
-        }
-      }
-
       test('allows an omitted environment', () {
         var pubspec = Pubspec.parse('name: testing', sources);
-        expectDefaultSdkConstraint(pubspec);
+        expect(
+          pubspec.dartSdkConstraint.effectiveConstraint,
+          VersionConstraint.parse('<2.0.0'),
+        );
+
         expect(pubspec.sdkConstraints, isNot(contains('flutter')));
         expect(pubspec.sdkConstraints, isNot(contains('fuchsia')));
       });
 
       test('default SDK constraint can be omitted with empty environment', () {
         var pubspec = Pubspec.parse('', sources);
-        expectDefaultSdkConstraint(pubspec);
+        expect(
+          pubspec.dartSdkConstraint.effectiveConstraint,
+          VersionConstraint.parse('<2.0.0'),
+        );
         expect(pubspec.sdkConstraints, isNot(contains('flutter')));
         expect(pubspec.sdkConstraints, isNot(contains('fuchsia')));
       });
@@ -535,7 +521,10 @@
   environment:
     sdk: ">1.0.0"
   ''', sources);
-        expectDefaultSdkConstraint(pubspec);
+        expect(
+          pubspec.dartSdkConstraint.effectiveConstraint,
+          VersionConstraint.parse('>1.0.0 <2.0.0'),
+        );
         expect(pubspec.sdkConstraints, isNot(contains('flutter')));
         expect(pubspec.sdkConstraints, isNot(contains('fuchsia')));
       });
@@ -547,8 +536,12 @@
   environment:
     sdk: ">3.0.0"
   ''', sources);
-        expect(pubspec.sdkConstraints,
-            containsPair('dart', VersionConstraint.parse('>3.0.0')));
+        expect(
+            pubspec.sdkConstraints,
+            containsPair(
+              'dart',
+              SdkConstraint(VersionConstraint.parse('>3.0.0')),
+            ));
         expect(pubspec.sdkConstraints, isNot(contains('flutter')));
         expect(pubspec.sdkConstraints, isNot(contains('fuchsia')));
       });
@@ -565,12 +558,23 @@
   flutter: ^0.1.2
   fuchsia: ^5.6.7
 ''', sources);
-        expect(pubspec.sdkConstraints,
-            containsPair('dart', VersionConstraint.parse('>=1.2.3 <2.3.4')));
-        expect(pubspec.sdkConstraints,
-            containsPair('flutter', VersionConstraint.parse('>=0.1.2')));
-        expect(pubspec.sdkConstraints,
-            containsPair('fuchsia', VersionConstraint.parse('^5.6.7')));
+        expect(
+            pubspec.sdkConstraints,
+            containsPair('dart',
+                SdkConstraint(VersionConstraint.parse('>=1.2.3 <2.3.4'))));
+        expect(
+            pubspec.sdkConstraints,
+            containsPair(
+                'flutter',
+                SdkConstraint(
+                  VersionConstraint.parse('>=0.1.2'),
+                  originalConstraint: VersionConstraint.parse('^0.1.2'),
+                )));
+        expect(
+          pubspec.sdkConstraints,
+          containsPair(
+              'fuchsia', SdkConstraint(VersionConstraint.parse('^5.6.7'))),
+        );
       });
 
       test("throws if the sdk isn't a string", () {
diff --git a/test/upgrade/upgrade_null_safety_test.dart b/test/upgrade/upgrade_null_safety_test.dart
index 557c589..ad210d8 100644
--- a/test/upgrade/upgrade_null_safety_test.dart
+++ b/test/upgrade/upgrade_null_safety_test.dart
@@ -441,7 +441,7 @@
         },
         error: allOf(
           contains('Because myapp depends on has_conflict >=2.0.0 which'),
-          contains('requires SDK version >=2.13.0 <3.0.0,'),
+          contains('requires SDK version >=2.13.0 <4.0.0,'),
           contains('version solving failed.'),
         ),
       );
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index 69489a3..8ade3f6 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -1172,7 +1172,7 @@
 
         await expectResolves(
           environment: {'_PUB_TEST_SDK_VERSION': '2.0.0-dev.99'},
-          // Log output should mention the PUB_ALLOW_RELEASE_SDK environment
+          // Log output should mention the PUB_ALLOW_PRERELEASE_SDK environment
           // variable and mention the foo and bar packages specifically.
           output: allOf(contains('PUB_ALLOW_PRERELEASE_SDK'),
               anyOf(contains('bar, foo'))),