[analyzer] Stop using Diagnostic.data and deprecate it.

The only two uses of `Diagnostic.data` have been replaced with
expandos keyed off the `Diagnostic` object. To make this possible, the
`at...` methods of `DiagnosticReporter` have been changed to return
the `Diagnostic` object they create. This paves the way for a future
CL series, in which I plan to change the API for reporting errors to a
more literate API in which the caller will have access to the
`Diagnostic` object.

Change-Id: I40820387abcd53daf934974c93106803d706fe07
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/444461
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_missing_switch_cases.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_missing_switch_cases.dart
index 0f29e15..378bcfa 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/add_missing_switch_cases.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/add_missing_switch_cases.dart
@@ -32,8 +32,8 @@
       return;
     }
 
-    var patternPartsList = diagnostic.data;
-    if (patternPartsList is! List<List<MissingPatternPart>>) {
+    var patternPartsList = MissingPatternPart.byDiagnostic[diagnostic];
+    if (patternPartsList == null) {
       return;
     }
 
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/pubspec/fix_generator.dart b/pkg/analysis_server/lib/src/services/correction/fix/pubspec/fix_generator.dart
index d1efa20..42c9399 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/pubspec/fix_generator.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/pubspec/fix_generator.dart
@@ -154,7 +154,7 @@
       defaultEol: endOfLine,
     );
 
-    var data = diagnostic.data as MissingDependencyData;
+    var data = MissingDependencyData.byDiagnostic[diagnostic]!;
     var addDeps = data.addDeps;
     var addDevDeps = data.addDevDeps;
     var removeDevDeps = data.removeDevDeps;
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 8dfdd7f..6d50b0a 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -22,6 +22,8 @@
 * Deprecate `ClassElement.isImplementableIn`; use `isImplementableOutside` instead.
 * Deprecate `ClassElement.isMixableIn`; use `isMixableOutside` instead.
 * Deprecate `MixinElement.isImplementableIn`; use `isImplementableOutside` instead.
+* Deprecate `Diagnostic.data`. Instead, please use an expando to attach data to
+  the `Diagnostic` object.
 
 ## 8.0.0
 * Remove deprecated element model V1.
diff --git a/pkg/analyzer/api.txt b/pkg/analyzer/api.txt
index f353dc3..0491cba 100644
--- a/pkg/analyzer/api.txt
+++ b/pkg/analyzer/api.txt
@@ -4763,8 +4763,8 @@
   buildSdkSummary (function: Future<Uint8List> Function({String? embedderYamlPath, required ResourceProvider resourceProvider, required String sdkPath}))
 package:analyzer/diagnostic/diagnostic.dart:
   Diagnostic (class extends Object):
-    forValues (constructor: Diagnostic Function({List<DiagnosticMessage> contextMessages, String? correctionMessage, Object? data, DiagnosticCode? diagnosticCode, deprecated DiagnosticCode? errorCode, required int length, required String message, required int offset, required Source source}))
-    tmp (constructor: Diagnostic Function({List<Object?> arguments, List<DiagnosticMessage> contextMessages, Object? data, DiagnosticCode? diagnosticCode, deprecated DiagnosticCode? errorCode, required int length, required int offset, required Source source}))
+    forValues (constructor: Diagnostic Function({List<DiagnosticMessage> contextMessages, String? correctionMessage, deprecated Object? data, DiagnosticCode? diagnosticCode, deprecated DiagnosticCode? errorCode, required int length, required String message, required int offset, required Source source}))
+    tmp (constructor: Diagnostic Function({List<Object?> arguments, List<DiagnosticMessage> contextMessages, deprecated Object? data, DiagnosticCode? diagnosticCode, deprecated DiagnosticCode? errorCode, required int length, required int offset, required Source source}))
     contextMessages (getter: List<DiagnosticMessage>)
     correction (getter: String?, deprecated)
     correctionMessage (getter: String?)
@@ -4874,13 +4874,13 @@
     lockLevel (getter: int)
     lockLevel= (setter: int)
     source (getter: Source)
-    atConstructorDeclaration (method: void Function(ConstructorDeclaration, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data}))
-    atElement2 (method: void Function(Element, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data}), experimental)
-    atEntity (method: void Function(SyntacticEntity, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data}))
-    atNode (method: void Function(AstNode, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data}))
-    atOffset (method: void Function({List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data, DiagnosticCode? diagnosticCode, deprecated DiagnosticCode? errorCode, required int length, required int offset}))
-    atSourceSpan (method: void Function(SourceSpan, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data}))
-    atToken (method: void Function(Token, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, Object? data}))
+    atConstructorDeclaration (method: Diagnostic Function(ConstructorDeclaration, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data}))
+    atElement2 (method: Diagnostic Function(Element, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data}), experimental)
+    atEntity (method: Diagnostic Function(SyntacticEntity, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data}))
+    atNode (method: Diagnostic Function(AstNode, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data}))
+    atOffset (method: Diagnostic Function({List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data, DiagnosticCode? diagnosticCode, deprecated DiagnosticCode? errorCode, required int length, required int offset}))
+    atSourceSpan (method: Diagnostic Function(SourceSpan, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data}))
+    atToken (method: Diagnostic Function(Token, DiagnosticCode, {List<Object>? arguments, List<DiagnosticMessage>? contextMessages, deprecated Object? data}))
     reportError (method: void Function(Diagnostic))
   RecordingDiagnosticListener (class extends Object implements AnalysisErrorListener, DiagnosticListener):
     new (constructor: RecordingDiagnosticListener Function())
diff --git a/pkg/analyzer/lib/diagnostic/diagnostic.dart b/pkg/analyzer/lib/diagnostic/diagnostic.dart
index 723fd43..682b0d3 100644
--- a/pkg/analyzer/lib/diagnostic/diagnostic.dart
+++ b/pkg/analyzer/lib/diagnostic/diagnostic.dart
@@ -24,6 +24,7 @@
   final List<DiagnosticMessage> contextMessages;
 
   /// Data associated with this diagnostic, specific for [diagnosticCode].
+  @Deprecated('Use an expando instead')
   final Object? data;
 
   /// A description of how to fix the problem, or `null` if there is no such
@@ -46,7 +47,7 @@
     required String message,
     this.correctionMessage,
     this.contextMessages = const [],
-    this.data,
+    @Deprecated('Use an expando instead') this.data,
   }) : diagnosticCode = _useNonNullCodeBetween(diagnosticCode, errorCode),
        problemMessage = DiagnosticMessageImpl(
          filePath: source.fullName,
@@ -72,7 +73,7 @@
     DiagnosticCode? errorCode,
     List<Object?> arguments = const [],
     List<DiagnosticMessage> contextMessages = const [],
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
     var code = _useNonNullCodeBetween(diagnosticCode, errorCode);
     assert(
@@ -98,6 +99,7 @@
       message: message,
       correctionMessage: correctionMessage,
       contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
       data: data,
     );
   }
diff --git a/pkg/analyzer/lib/error/listener.dart b/pkg/analyzer/lib/error/listener.dart
index c2440ca..2efdae8 100644
--- a/pkg/analyzer/lib/error/listener.dart
+++ b/pkg/analyzer/lib/error/listener.dart
@@ -96,47 +96,54 @@
   /// Reports a diagnostic with the given [diagnosticCode] and [arguments].
   ///
   /// The location of the diagnostic will be the name of the [node].
-  void atConstructorDeclaration(
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
+  Diagnostic atConstructorDeclaration(
     ConstructorDeclaration node,
     DiagnosticCode diagnosticCode, {
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
     // TODO(brianwilkerson): Consider extending this method to take any
     //  declaration and compute the correct range for the name of that
     //  declaration. This might make it easier to be consistent.
     if (node.name case var nameToken?) {
       var offset = node.returnType.offset;
-      atOffset(
+      return atOffset(
         offset: offset,
         length: nameToken.end - offset,
         diagnosticCode: diagnosticCode,
         arguments: arguments,
       );
     } else {
-      atNode(node.returnType, diagnosticCode, arguments: arguments);
+      return atNode(node.returnType, diagnosticCode, arguments: arguments);
     }
   }
 
   /// Reports a diagnostic with the given [diagnosticCode] and [arguments].
   ///
   /// The [element] is used to compute the location of the diagnostic.
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
   @experimental
-  void atElement2(
+  Diagnostic atElement2(
     Element element,
     DiagnosticCode diagnosticCode, {
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
     var nonSynthetic = element.nonSynthetic;
-    atOffset(
+    return atOffset(
       diagnosticCode: diagnosticCode,
       offset: nonSynthetic.firstFragment.nameOffset ?? -1,
       length: nonSynthetic.name?.length ?? 0,
       arguments: arguments,
       contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
       data: data,
     );
   }
@@ -144,19 +151,23 @@
   /// Reports a diagnostic with the given [diagnosticCode] and [arguments].
   ///
   /// The [entity] is used to compute the location of the diagnostic.
-  void atEntity(
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
+  Diagnostic atEntity(
     SyntacticEntity entity,
     DiagnosticCode diagnosticCode, {
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
-    atOffset(
+    return atOffset(
       diagnosticCode: diagnosticCode,
       offset: entity.offset,
       length: entity.length,
       arguments: arguments,
       contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
       data: data,
     );
   }
@@ -164,19 +175,23 @@
   /// Reports a diagnostic with the given [diagnosticCode] and [arguments].
   ///
   /// The [node] is used to compute the location of the diagnostic.
-  void atNode(
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
+  Diagnostic atNode(
     AstNode node,
     DiagnosticCode diagnosticCode, {
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
-    atOffset(
+    return atOffset(
       diagnosticCode: diagnosticCode,
       offset: node.offset,
       length: node.length,
       arguments: arguments,
       contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
       data: data,
     );
   }
@@ -186,18 +201,18 @@
   ///
   /// The location of the diagnostic is specified by the given [offset] and
   /// [length].
-  void atOffset({
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
+  Diagnostic atOffset({
     required int offset,
     required int length,
     @Deprecated("Use 'diagnosticCode' instead") DiagnosticCode? errorCode,
     DiagnosticCode? diagnosticCode,
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
-    if (lockLevel != 0) {
-      return;
-    }
     if ((errorCode == null && diagnosticCode == null) ||
         (errorCode != null && diagnosticCode != null)) {
       throw ArgumentError(
@@ -225,35 +240,40 @@
 
     contextMessages ??= [];
     contextMessages.addAll(convertTypeNames(arguments));
-    _diagnosticListener.onDiagnostic(
-      Diagnostic.tmp(
-        source: _source,
-        offset: offset,
-        length: length,
-        diagnosticCode: diagnosticCode,
-        arguments: arguments ?? const [],
-        contextMessages: contextMessages,
-        data: data,
-      ),
+    var diagnostic = Diagnostic.tmp(
+      source: _source,
+      offset: offset,
+      length: length,
+      diagnosticCode: diagnosticCode,
+      arguments: arguments ?? const [],
+      contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
+      data: data,
     );
+    reportError(diagnostic);
+    return diagnostic;
   }
 
   /// Reports a diagnostic with the given [diagnosticCode] and [arguments].
   ///
   /// The [span] is used to compute the location of the diagnostic.
-  void atSourceSpan(
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
+  Diagnostic atSourceSpan(
     SourceSpan span,
     DiagnosticCode diagnosticCode, {
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
-    atOffset(
+    return atOffset(
       diagnosticCode: diagnosticCode,
       offset: span.start.offset,
       length: span.length,
       arguments: arguments,
       contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
       data: data,
     );
   }
@@ -261,25 +281,32 @@
   /// Reports a diagnostic with the given [diagnosticCode] and [arguments].
   ///
   /// The [token] is used to compute the location of the diagnostic.
-  void atToken(
+  ///
+  /// The reported [Diagnostic] is returned so that the caller may attach
+  /// additional information to it (for example, using an expando).
+  Diagnostic atToken(
     Token token,
     DiagnosticCode diagnosticCode, {
     List<Object>? arguments,
     List<DiagnosticMessage>? contextMessages,
-    Object? data,
+    @Deprecated('Use an expando instead') Object? data,
   }) {
-    atOffset(
+    return atOffset(
       diagnosticCode: diagnosticCode,
       offset: token.offset,
       length: token.length,
       arguments: arguments,
       contextMessages: contextMessages,
+      // ignore: deprecated_member_use_from_same_package
       data: data,
     );
   }
 
   /// Report the given [diagnostic].
   void reportError(Diagnostic diagnostic) {
+    if (lockLevel != 0) {
+      return;
+    }
     _diagnosticListener.onDiagnostic(diagnostic);
   }
 }
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index 3dc919d..c6c0937 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -1115,7 +1115,7 @@
               correctionData.add(correctionDataBuffer.parts);
             }
           }
-          _diagnosticReporter.atToken(
+          var diagnostic = _diagnosticReporter.atToken(
             switchKeyword,
             isSwitchExpression
                 ? CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_EXPRESSION
@@ -1125,8 +1125,10 @@
               errorBuffer.toString(),
               correctionTextBuffer.toString(),
             ],
-            data: correctionData.isNotEmpty ? correctionData : null,
           );
+          if (correctionData.isNotEmpty) {
+            MissingPatternPart.byDiagnostic[diagnostic] = correctionData;
+          }
         }
       } else {
         if (defaultNode != null && mustBeExhaustive) {
diff --git a/pkg/analyzer/lib/src/error/literal_element_verifier.dart b/pkg/analyzer/lib/src/error/literal_element_verifier.dart
index 59ba9a2..02bb419 100644
--- a/pkg/analyzer/lib/src/error/literal_element_verifier.dart
+++ b/pkg/analyzer/lib/src/error/literal_element_verifier.dart
@@ -257,10 +257,11 @@
     );
 
     if (iterableType == null) {
-      return _diagnosticReporter.atNode(
+      _diagnosticReporter.atNode(
         expression,
         CompileTimeErrorCode.NOT_ITERABLE_SPREAD,
       );
+      return;
     }
 
     var iterableElementType = iterableType.typeArguments[0];
@@ -357,10 +358,11 @@
     var mapType = expressionType.asInstanceOf(typeProvider.mapElement);
 
     if (mapType == null) {
-      return _diagnosticReporter.atNode(
+      _diagnosticReporter.atNode(
         expression,
         CompileTimeErrorCode.NOT_MAP_SPREAD,
       );
+      return;
     }
 
     var keyType = mapType.typeArguments[0];
diff --git a/pkg/analyzer/lib/src/generated/exhaustiveness.dart b/pkg/analyzer/lib/src/generated/exhaustiveness.dart
index a42374d..46edd65 100644
--- a/pkg/analyzer/lib/src/generated/exhaustiveness.dart
+++ b/pkg/analyzer/lib/src/generated/exhaustiveness.dart
@@ -476,7 +476,14 @@
   String toString() => value2.name!;
 }
 
-abstract class MissingPatternPart {}
+abstract class MissingPatternPart {
+  /// Expando associating each
+  /// [CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_EXPRESSION] or
+  /// [CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH_STATEMENT] diagnostic with a
+  /// list of missing patterns; this data is used by the analysis server to add
+  /// missing switch cases.
+  static final byDiagnostic = Expando<List<List<MissingPatternPart>>>();
+}
 
 class MissingPatternTextPart extends MissingPatternPart {
   final String text;
diff --git a/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart b/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
index d55bedb..dca0eb1 100644
--- a/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
+++ b/pkg/analyzer/lib/src/pubspec/pubspec_validator.dart
@@ -165,7 +165,6 @@
     DiagnosticCode diagnosticCode, [
     List<Object>? arguments,
     List<DiagnosticMessage>? messages,
-    Object? data,
   ]) {
     var span = node.span;
     reporter.atOffset(
@@ -174,7 +173,6 @@
       diagnosticCode: diagnosticCode,
       arguments: arguments,
       contextMessages: messages,
-      data: data,
     );
   }
 }
diff --git a/pkg/analyzer/lib/src/pubspec/validators/missing_dependency_validator.dart b/pkg/analyzer/lib/src/pubspec/validators/missing_dependency_validator.dart
index 9f3ff29..66a6337 100644
--- a/pkg/analyzer/lib/src/pubspec/validators/missing_dependency_validator.dart
+++ b/pkg/analyzer/lib/src/pubspec/validators/missing_dependency_validator.dart
@@ -12,6 +12,12 @@
 import 'package:yaml/yaml.dart';
 
 class MissingDependencyData {
+  /// Expando associating each [PubspecWarningCode.MISSING_DEPENDENCY] with
+  /// missing dependency information; this information is used by the analysis
+  /// server to list of missing patterns; this data is used by the analysis
+  /// server to compute fixes.
+  static final byDiagnostic = Expando<MissingDependencyData>();
+
   final List<String> addDeps;
   final List<String> addDevDeps;
   final List<String> removeDevDeps;
@@ -127,33 +133,37 @@
           "$message ${addDevDeps.map((s) => "'$s'").join(',')} in 'dev_dependencies'";
     }
     if (addDeps.isNotEmpty || addDevDeps.isNotEmpty) {
-      _reportErrorForNode(
+      var diagnostic = _reportErrorForNode(
         contents.nodes.values.first,
         PubspecWarningCode.MISSING_DEPENDENCY,
         [message],
         [],
-        MissingDependencyData(addDeps, addDevDeps, removeDevDeps),
+      );
+      MissingDependencyData.byDiagnostic[diagnostic] = MissingDependencyData(
+        addDeps,
+        addDevDeps,
+        removeDevDeps,
       );
     }
     return recorder.diagnostics;
   }
 
   /// Report an error for the given node.
-  void _reportErrorForNode(
+  ///
+  /// The reported [Diagnostic] is returned.
+  Diagnostic _reportErrorForNode(
     YamlNode node,
     DiagnosticCode diagnosticCode, [
     List<Object>? arguments,
     List<DiagnosticMessage>? messages,
-    Object? data,
   ]) {
     var span = node.span;
-    reporter.atOffset(
+    return reporter.atOffset(
       offset: span.start.offset,
       length: span.length,
       diagnosticCode: diagnosticCode,
       arguments: arguments,
       contextMessages: messages,
-      data: data,
     );
   }
 }
diff --git a/pkg/analyzer/test/src/pubspec/diagnostics/missing_dependency_test.dart b/pkg/analyzer/test/src/pubspec/diagnostics/missing_dependency_test.dart
index 70113f3..acae8a9 100644
--- a/pkg/analyzer/test/src/pubspec/diagnostics/missing_dependency_test.dart
+++ b/pkg/analyzer/test/src/pubspec/diagnostics/missing_dependency_test.dart
@@ -34,7 +34,7 @@
     List<String> removeDevDeps = const [],
   }) {
     var error = _runValidator(content, usedDeps, usedDevDeps).first;
-    var data = error.data as MissingDependencyData;
+    var data = MissingDependencyData.byDiagnostic[error]!;
     expect(error.diagnosticCode, PubspecWarningCode.MISSING_DEPENDENCY);
     expect(data.addDeps, addDeps);
     expect(data.addDevDeps, addDevDeps);