Respect upper bound of Flutter constraint in root packages after 3.9 (#4595)

diff --git a/lib/src/language_version.dart b/lib/src/language_version.dart
index fdfb2bf..0f5fffa 100644
--- a/lib/src/language_version.dart
+++ b/lib/src/language_version.dart
@@ -70,6 +70,9 @@
   bool get forbidsUnknownDescriptionKeys =>
       this >= firstVersionForbidingUnknownDescriptionKeys;
 
+  bool get respectsFlutterBoundInRoots =>
+      this >= firstVersionRespectingFlutterBoundInRoots;
+
   /// Minimum language version at which short hosted syntax is supported.
   ///
   /// This allows `hosted` dependencies to be expressed as:
@@ -112,6 +115,10 @@
     3,
     7,
   );
+  static const firstVersionRespectingFlutterBoundInRoots = LanguageVersion(
+    3,
+    9,
+  );
 
   /// Transform language version to string that can be parsed with
   /// [LanguageVersion.parse].
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart
index dc688e9..f514406 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -10,6 +10,7 @@
 import 'package:yaml/yaml.dart';
 
 import 'io.dart';
+import 'language_version.dart';
 import 'package_name.dart';
 import 'pubspec.dart';
 import 'system_cache.dart';
@@ -149,6 +150,8 @@
             ),
             'flutter' => SdkConstraint.interpretFlutterSdkConstraint(
               originalConstraint,
+              isRoot: false,
+              languageVersion: LanguageVersion.defaultLanguageVersion,
             ),
             _ => SdkConstraint(originalConstraint),
           };
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 5ebc55f..62eaeb9 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -225,15 +225,12 @@
         _FileType.pubspec,
       );
     }
-    final constraints = {
-      'dart': SdkConstraint.interpretDartSdkConstraint(
-        originalDartSdkConstraint,
-        defaultUpperBoundConstraint:
-            _includeDefaultSdkConstraint
-                ? _defaultUpperBoundSdkConstraint
-                : null,
-      ),
-    };
+    final dartConstraint = SdkConstraint.interpretDartSdkConstraint(
+      originalDartSdkConstraint,
+      defaultUpperBoundConstraint:
+          _includeDefaultSdkConstraint ? _defaultUpperBoundSdkConstraint : null,
+    );
+    final constraints = {'dart': dartConstraint};
 
     if (yaml is YamlMap) {
       yaml.nodes.forEach((nameNode, constraintNode) {
@@ -253,7 +250,11 @@
         );
         constraints[name] =
             name == 'flutter'
-                ? SdkConstraint.interpretFlutterSdkConstraint(constraint)
+                ? SdkConstraint.interpretFlutterSdkConstraint(
+                  constraint,
+                  isRoot: _containingDescription is ResolvedRootDescription,
+                  languageVersion: dartConstraint.languageVersion,
+                )
                 : SdkConstraint(constraint);
       });
     }
@@ -816,17 +817,26 @@
     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.
+  /// Flutter constraints get special treatment, as Flutter won't be using
+  /// semantic versioning to mark breaking releases. We simply ignore upper
+  /// bounds for dependencies.
+  ///
+  /// After language version
+  /// [LanguageVersion.firstVersionRespectingFlutterBoundInRoots] for root
+  /// packages we use the upper bound, allowing app developers to constrain the
+  /// Flutter version.
   factory SdkConstraint.interpretFlutterSdkConstraint(
-    VersionConstraint constraint,
-  ) {
+    VersionConstraint constraint, {
+    required bool isRoot,
+    required LanguageVersion languageVersion,
+  }) {
     if (constraint is VersionRange) {
-      return SdkConstraint(
-        VersionRange(min: constraint.min, includeMin: constraint.includeMin),
-        originalConstraint: constraint,
-      );
+      if (!(isRoot && languageVersion.respectsFlutterBoundInRoots)) {
+        return SdkConstraint(
+          VersionRange(min: constraint.min, includeMin: constraint.includeMin),
+          originalConstraint: constraint,
+        );
+      }
     }
     return SdkConstraint(constraint);
   }
diff --git a/test/get/flutter_constraint_upper_bound_ignored_test.dart b/test/get/flutter_constraint_upper_bound_ignored_test.dart
index 3491746..0620211 100644
--- a/test/get/flutter_constraint_upper_bound_ignored_test.dart
+++ b/test/get/flutter_constraint_upper_bound_ignored_test.dart
@@ -9,21 +9,116 @@
 import '../test_pub.dart';
 
 void main() {
-  test('pub get succeeds despite of "invalid" flutter upper bound', () async {
+  test(
+    'pub get succeeds despite of "invalid" flutter upper bound in dependency',
+    () async {
+      final fakeFlutterRoot = d.dir('fake_flutter_root', [
+        d.flutterVersion('1.23.0'),
+      ]);
+      await fakeFlutterRoot.create();
+
+      final server = await servePackages();
+      server.serve(
+        'foo',
+        '1.0.0',
+        pubspec: {
+          'environment': {'sdk': '^$testVersion', 'flutter': '>=0.5.0 <1.0.0'},
+        },
+      );
+
+      await d.appDir(dependencies: {'foo': '^1.0.0'}).create();
+
+      await pubGet(
+        exitCode: exit_codes.SUCCESS,
+        environment: {'FLUTTER_ROOT': fakeFlutterRoot.io.path},
+      );
+    },
+  );
+
+  test('pub get ignores the bound of the root package before 3.10', () async {
     final fakeFlutterRoot = d.dir('fake_flutter_root', [
       d.flutterVersion('1.23.0'),
     ]);
     await fakeFlutterRoot.create();
+
+    await d
+        .appDir(
+          pubspec: {
+            'environment': {'sdk': '^3.8.0', 'flutter': '>=0.5.0 <1.0.0'},
+          },
+        )
+        .create();
+
+    await pubGet(
+      environment: {
+        'FLUTTER_ROOT': fakeFlutterRoot.io.path,
+        '_PUB_TEST_SDK_VERSION': '3.9.0',
+      },
+    );
+  });
+
+  test('pub get respects the bound of the root package after 3.9', () async {
+    final fakeFlutterRoot = d.dir('fake_flutter_root', [
+      d.flutterVersion('1.23.0'),
+    ]);
+    await fakeFlutterRoot.create();
+
+    await d
+        .appDir(
+          pubspec: {
+            'environment': {'sdk': '^3.9.0', 'flutter': '>=0.5.0 <1.0.0'},
+          },
+        )
+        .create();
+
+    await pubGet(
+      exitCode: 1,
+      environment: {
+        'FLUTTER_ROOT': fakeFlutterRoot.io.path,
+        '_PUB_TEST_SDK_VERSION': '3.9.0',
+      },
+      error: contains(
+        'Because myapp requires '
+        'Flutter SDK version >=0.5.0 <1.0.0, version solving failed',
+      ),
+    );
+  });
+
+  test('pub get respects the bound of a workspace root package', () async {
+    final fakeFlutterRoot = d.dir('fake_flutter_root', [
+      d.flutterVersion('1.23.0'),
+    ]);
+    await fakeFlutterRoot.create();
+
     await d.dir(appPath, [
-      d.pubspec({
-        'name': 'myapp',
-        'environment': {'flutter': '>=0.5.0 <1.0.0'},
-      }),
+      d.appPubspec(
+        extras: {
+          'environment': {'sdk': '^3.9.0'},
+          'workspace': ['app'],
+        },
+      ),
+      d.dir('app', [
+        d.libPubspec(
+          'app',
+          '1.0.0',
+          resolutionWorkspace: true,
+          extras: {
+            'environment': {'sdk': '^3.9.0', 'flutter': '>=0.5.0 <1.0.0'},
+          },
+        ),
+      ]),
     ]).create();
 
     await pubGet(
-      exitCode: exit_codes.SUCCESS,
-      environment: {'FLUTTER_ROOT': fakeFlutterRoot.io.path},
+      exitCode: 1,
+      environment: {
+        '_PUB_TEST_SDK_VERSION': '3.9.0',
+        'FLUTTER_ROOT': fakeFlutterRoot.io.path,
+      },
+      error: contains(
+        'Because app requires '
+        'Flutter SDK version >=0.5.0 <1.0.0, version solving failed',
+      ),
     );
   });
 }
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index a3e2ba3..11892a1 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -793,7 +793,7 @@
         final pubspec = Pubspec.parse(
           '''
 environment:
-  sdk: ">=1.2.3 <2.3.4"
+  sdk: ">=3.10.3 <3.11.4"
   flutter: ^0.1.2
   fuchsia: ^5.6.7
 ''',
@@ -804,17 +804,14 @@
           pubspec.sdkConstraints,
           containsPair(
             'dart',
-            SdkConstraint(VersionConstraint.parse('>=1.2.3 <2.3.4')),
+            SdkConstraint(VersionConstraint.parse('>=3.10.3 <3.11.4')),
           ),
         );
         expect(
           pubspec.sdkConstraints,
           containsPair(
             'flutter',
-            SdkConstraint(
-              VersionConstraint.parse('>=0.1.2'),
-              originalConstraint: VersionConstraint.parse('^0.1.2'),
-            ),
+            SdkConstraint(VersionConstraint.parse('^0.1.2')),
           ),
         );
         expect(