Use a non-null index for all features, even expired ones.

This ensures that the FeatureSet object can track the presence or
absence of expired features.

Change-Id: Ie6e213993792b8c3d6cf84ef01c464c16c274b85
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/99920
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/experiments.dart b/pkg/analyzer/lib/src/dart/analysis/experiments.dart
index 8a5f2f1..a9207abe 100644
--- a/pkg/analyzer/lib/src/dart/analysis/experiments.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/experiments.dart
@@ -68,7 +68,7 @@
       'Non Nullable');
 
   static const control_flow_collections = const ExperimentalFeature(
-      null,
+      2,
       EnableString.control_flow_collections,
       IsEnabledByDefault.control_flow_collections,
       IsExpired.control_flow_collections,
@@ -76,7 +76,7 @@
       firstSupportedVersion: '2.2.2');
 
   static const spread_collections = const ExperimentalFeature(
-      null,
+      3,
       EnableString.spread_collections,
       IsEnabledByDefault.spread_collections,
       IsExpired.spread_collections,
@@ -84,7 +84,7 @@
       firstSupportedVersion: '2.2.2');
 
   static const set_literals = const ExperimentalFeature(
-      null,
+      4,
       EnableString.set_literals,
       IsEnabledByDefault.set_literals,
       IsExpired.set_literals,
@@ -92,21 +92,21 @@
       firstSupportedVersion: '2.2.0');
 
   static const triple_shift = const ExperimentalFeature(
-      2,
+      5,
       EnableString.triple_shift,
       IsEnabledByDefault.triple_shift,
       IsExpired.triple_shift,
       'Triple-shift operator');
 
   static const bogus_disabled = const ExperimentalFeature(
-      null,
+      6,
       EnableString.bogus_disabled,
       IsEnabledByDefault.bogus_disabled,
       IsExpired.bogus_disabled,
       null);
 
   static const bogus_enabled = const ExperimentalFeature(
-      null,
+      7,
       EnableString.bogus_enabled,
       IsEnabledByDefault.bogus_enabled,
       IsExpired.bogus_enabled,
@@ -145,7 +145,12 @@
       : _enableFlags = <bool>[
           constant_update_2018 ?? IsEnabledByDefault.constant_update_2018,
           non_nullable ?? IsEnabledByDefault.non_nullable,
+          true, // control-flow-collections
+          true, // spread-collections
+          true, // set-literals
           triple_shift ?? IsEnabledByDefault.triple_shift,
+          false, // bogus-disabled
+          true, // bogus-enabled
         ];
 
   /// Computes a set of features for use in a unit test.  Computes the set of
@@ -169,17 +174,19 @@
 
   ExperimentStatus._(this._enableFlags);
 
-  /// Hardcoded state for the expired flag "bogus_disabled"
-  bool get bogus_disabled => false;
+  /// Current state for the flag "bogus_disabled"
+  bool get bogus_disabled => isEnabled(ExperimentalFeatures.bogus_disabled);
 
-  /// Hardcoded state for the expired flag "bogus_enabled"
-  bool get bogus_enabled => true;
+  /// Current state for the flag "bogus_enabled"
+  bool get bogus_enabled => isEnabled(ExperimentalFeatures.bogus_enabled);
 
   /// Current state for the flag "constant-update-2018"
-  bool get constant_update_2018 => _enableFlags[0];
+  bool get constant_update_2018 =>
+      isEnabled(ExperimentalFeatures.constant_update_2018);
 
   /// Current state for the flag "control_flow_collections"
-  bool get control_flow_collections => true;
+  bool get control_flow_collections =>
+      isEnabled(ExperimentalFeatures.control_flow_collections);
 
   @override
   int get hashCode {
@@ -191,16 +198,17 @@
   }
 
   /// Current state for the flag "non-nullable"
-  bool get non_nullable => _enableFlags[1];
+  bool get non_nullable => isEnabled(ExperimentalFeatures.non_nullable);
 
   /// Current state for the flag "set-literals"
-  bool get set_literals => true;
+  bool get set_literals => isEnabled(ExperimentalFeatures.set_literals);
 
   /// Current state for the flag "spread_collections"
-  bool get spread_collections => true;
+  bool get spread_collections =>
+      isEnabled(ExperimentalFeatures.spread_collections);
 
   /// Current state for the flag "triple_shift"
-  bool get triple_shift => _enableFlags[2];
+  bool get triple_shift => isEnabled(ExperimentalFeatures.triple_shift);
 
   @override
   operator ==(Object other) {
@@ -215,9 +223,7 @@
   }
 
   /// Queries whether the given [feature] is enabled or disabled.
-  bool isEnabled(ExperimentalFeature feature) => feature.isExpired
-      ? feature.isEnabledByDefault
-      : _enableFlags[feature.index];
+  bool isEnabled(ExperimentalFeature feature) => _enableFlags[feature.index];
 
   @override
   FeatureSet restrictToVersion(Version version) =>
diff --git a/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart b/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart
index 9def415..ebf565c 100644
--- a/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart
@@ -19,14 +19,9 @@
 /// unrecognized flags are ignored, conflicting flags are resolved in favor of
 /// the flag appearing last.
 List<bool> decodeFlags(List<String> flags) {
-  var decodedFlags = <bool>[];
+  var decodedFlags = List<bool>.filled(_knownFeatures.length, false);
   for (var feature in _knownFeatures.values) {
-    if (feature.isExpired) continue;
-    var index = feature.index;
-    while (decodedFlags.length <= index) {
-      decodedFlags.add(false);
-    }
-    decodedFlags[index] = feature.isEnabledByDefault;
+    decodedFlags[feature.index] = feature.isEnabledByDefault;
   }
   for (var entry in _flagStringsToMap(flags).entries) {
     decodedFlags[entry.key] = entry.value;
@@ -46,11 +41,6 @@
     flags = restrictEnableFlagsToVersion(flags, Version.parse(sdkVersion));
   }
   for (ExperimentalFeature feature in additionalFeatures) {
-    if (feature.isExpired) {
-      // At the moment we can't enable features in the "expired" state.
-      // TODO(paulberry): fix this by including such features in enable flags.
-      continue;
-    }
     flags[feature.index] = true;
   }
   return flags;
@@ -93,11 +83,6 @@
 List<bool> restrictEnableFlagsToVersion(List<bool> flags, Version version) {
   flags = List.from(flags);
   for (var feature in _knownFeatures.values) {
-    if (feature.isExpired) {
-      // At the moment we can't disable features in the "expired" state.
-      // TODO(paulberry): fix this by including such features in enable flags.
-      continue;
-    }
     if (!feature.isEnabledByDefault ||
         feature.firstSupportedVersion > version) {
       flags[feature.index] = false;
@@ -251,9 +236,6 @@
   /// Index of the flag in the private data structure maintained by
   /// [ExperimentStatus].
   ///
-  /// For expired features, the index should be null, since no enable/disable
-  /// state needs to be stored.
-  ///
   /// This index should not be relied upon to be stable over time.  For instance
   /// it should not be used to serialize the state of experiments to long term
   /// storage if there is any expectation of compatibility between analyzer
@@ -279,7 +261,7 @@
       this.isEnabledByDefault, this.isExpired, this.documentation,
       {String firstSupportedVersion})
       : _firstSupportedVersion = firstSupportedVersion,
-        assert(isExpired ? index == null : index != null),
+        assert(index != null),
         assert(isEnabledByDefault
             ? firstSupportedVersion != null
             : firstSupportedVersion == null);
diff --git a/pkg/analyzer/test/src/dart/analysis/experiments_test.dart b/pkg/analyzer/test/src/dart/analysis/experiments_test.dart
index cb8ba93..740c46f 100644
--- a/pkg/analyzer/test/src/dart/analysis/experiments_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/experiments_test.dart
@@ -80,28 +80,28 @@
 
   test_fromStrings_illegal_use_of_expired_flag_disable() {
     // Expired flags are ignored even if they would fail validation.
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', true, true, 'a',
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', true, true, 'a',
         firstSupportedVersion: '1.0.0');
-    expect(getFlags(fromStrings(['no-a'])), []);
+    expect(getFlags(fromStrings(['no-a'])), [true]);
   }
 
   test_fromStrings_illegal_use_of_expired_flag_enable() {
     // Expired flags are ignored even if they would fail validation.
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', false, true, 'a');
-    expect(getFlags(fromStrings(['a'])), []);
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', false, true, 'a');
+    expect(getFlags(fromStrings(['a'])), [false]);
   }
 
   test_fromStrings_unnecessary_use_of_expired_flag_disable() {
     // Expired flags are ignored.
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', false, true, 'a');
-    expect(getFlags(fromStrings(['no-a'])), []);
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', false, true, 'a');
+    expect(getFlags(fromStrings(['no-a'])), [false]);
   }
 
   test_fromStrings_unnecessary_use_of_expired_flag_enable() {
     // Expired flags are ignored.
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', true, true, 'a',
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', true, true, 'a',
         firstSupportedVersion: '1.0.0');
-    expect(getFlags(fromStrings(['a'])), []);
+    expect(getFlags(fromStrings(['a'])), [true]);
   }
 
   test_fromStrings_unrecognized_flag() {
@@ -178,7 +178,7 @@
   }
 
   test_validateFlags_illegal_use_of_expired_flag_disable() {
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', true, true, 'a',
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', true, true, 'a',
         firstSupportedVersion: '1.0.0');
     var validationResult = getValidationResult(['no-a']);
     expect(validationResult, hasLength(1));
@@ -190,7 +190,7 @@
   }
 
   test_validateFlags_illegal_use_of_expired_flag_enable() {
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', false, true, 'a');
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', false, true, 'a');
     var validationResult = getValidationResult(['a']);
     expect(validationResult, hasLength(1));
     var error = validationResult[0] as IllegalUseOfExpiredFlag;
@@ -201,7 +201,7 @@
   }
 
   test_validateFlags_unnecessary_use_of_expired_flag_disable() {
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', false, true, 'a');
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', false, true, 'a');
     var validationResult = getValidationResult(['no-a']);
     expect(validationResult, hasLength(1));
     var error = validationResult[0] as UnnecessaryUseOfExpiredFlag;
@@ -212,7 +212,7 @@
   }
 
   test_validateFlags_unnecessary_use_of_expired_flag_enable() {
-    knownFeatures['a'] = ExperimentalFeature(null, 'a', true, true, 'a',
+    knownFeatures['a'] = ExperimentalFeature(0, 'a', true, true, 'a',
         firstSupportedVersion: '1.0.0');
     var validationResult = getValidationResult(['a']);
     expect(validationResult, hasLength(1));