Track the `VariableElement` associated with constant `DartObject`s.

The motivation for this functionality is lints which inspect the
declaration of constant values within Dart programs. By giving lints a
small amount of visibility into the intermediate values which were used
to construct constant values, it is possible for lints to validate not
just the resulting constant, but the means by which that constant was
constructed.

Change-Id: I75b3f46222ced88a0c7a7f7887de2994fcfa3f68
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251682
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/dart/constant/value.dart b/pkg/analyzer/lib/dart/constant/value.dart
index 12c83db..e396610 100644
--- a/pkg/analyzer/lib/dart/constant/value.dart
+++ b/pkg/analyzer/lib/dart/constant/value.dart
@@ -64,6 +64,9 @@
   /// would return `false` from [hasKnownValue].
   DartType? get type;
 
+  /// If this object is the value of a constant variable, the variable.
+  VariableElement? get variable;
+
   /// Return a representation of the value of the field with the given [name].
   ///
   /// Return `null` if either the object being represented does not have a field
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index 4621e73..5b205c4 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -107,6 +107,9 @@
                 constantInitializer,
                 [dartObject.type, constant.type]);
           }
+
+          // Associate with the variable.
+          dartObject = DartObjectImpl.forVariable(dartObject, constant);
         }
 
         if (dartObject != null) {
diff --git a/pkg/analyzer/lib/src/dart/constant/value.dart b/pkg/analyzer/lib/src/dart/constant/value.dart
index 04506a4..dabfac8 100644
--- a/pkg/analyzer/lib/src/dart/constant/value.dart
+++ b/pkg/analyzer/lib/src/dart/constant/value.dart
@@ -159,8 +159,18 @@
   /// The state of the object.
   final InstanceState _state;
 
+  @override
+  final VariableElement? variable;
+
   /// Initialize a newly created object to have the given [type] and [_state].
-  DartObjectImpl(this._typeSystem, this.type, this._state);
+  DartObjectImpl(this._typeSystem, this.type, this._state, {this.variable});
+
+  /// Creates a duplicate instance of [other], tied to [variable].
+  factory DartObjectImpl.forVariable(
+      DartObjectImpl other, VariableElement variable) {
+    return DartObjectImpl(other._typeSystem, other.type, other._state,
+        variable: variable);
+  }
 
   /// Create an object to represent an unknown value.
   factory DartObjectImpl.validWithUnknownValue(
diff --git a/pkg/analyzer/test/generated/constant_test.dart b/pkg/analyzer/test/generated/constant_test.dart
index e4a67c3..bc1414f 100644
--- a/pkg/analyzer/test/generated/constant_test.dart
+++ b/pkg/analyzer/test/generated/constant_test.dart
@@ -66,6 +66,7 @@
     var x_result = findElement.topVar('x').evaluationResult;
     assertDartObjectText(x_result.value, r'''
 dynamic <unknown>
+  variable: self::@variable::x
 ''');
   }
 
@@ -404,12 +405,14 @@
 E
   _name: String v1
   index: int 0
+  variable: self::@variable::x1
 ''');
 
     _assertTopVarConstValue('x2', r'''
 E
   _name: String v2
   index: int 1
+  variable: self::@variable::x2
 ''');
   }
 
@@ -430,7 +433,9 @@
     _name: String v1
     a: int 42
     index: int 0
+    variable: self::@enum::E::@field::v1
   index: int 1
+  variable: self::@enum::E::@field::v2
 ''');
   }
 
@@ -452,6 +457,7 @@
   _name: String v1
   f: double 10.0
   index: int 0
+  variable: self::@variable::x1
 ''');
 
     _assertTopVarConstValue('x2', r'''
@@ -459,6 +465,7 @@
   _name: String v2
   f: int 20
   index: int 1
+  variable: self::@variable::x2
 ''');
   }
 
@@ -482,6 +489,7 @@
   _name: String v1
   f: int 10
   index: int 0
+  variable: self::@variable::x1
 ''');
 
     _assertTopVarConstValue('x2', r'''
@@ -489,6 +497,7 @@
   _name: String v2
   f: int 20
   index: int 1
+  variable: self::@variable::x2
 ''');
 
     _assertTopVarConstValue('x3', r'''
@@ -496,6 +505,7 @@
   _name: String v3
   f: String abc
   index: int 2
+  variable: self::@variable::x3
 ''');
   }
 
@@ -594,6 +604,7 @@
     a: int 1
     b: int 2
   c: int 3
+  variable: self::@variable::x
 ''');
   }
 
@@ -620,6 +631,7 @@
     a: int 1
     b: int 2
   c: int 3
+  variable: self::@variable::x
 ''');
   }
 
@@ -644,6 +656,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -668,6 +681,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -692,6 +706,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -716,6 +731,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -740,6 +756,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -764,6 +781,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -788,6 +806,7 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
 ''');
   }
 
@@ -812,6 +831,47 @@
   (super): A
     a: int 1
   b: int 2
+  variable: self::@variable::x
+''');
+  }
+
+  test_variable_alias() async {
+    await resolveTestCode('''
+const a = 42;
+const b = a;
+''');
+
+    final a_result = findElement.topVar('a').evaluationResult;
+    assertDartObjectText(a_result.value, r'''
+int 42
+  variable: self::@variable::a
+''');
+
+    final b_result = findElement.topVar('b').evaluationResult;
+    assertDartObjectText(b_result.value, r'''
+int 42
+  variable: self::@variable::b
+''');
+  }
+
+  test_variable_list_elements() async {
+    await resolveTestCode('''
+const a = 0;
+const b = 2;
+const c = [a, 1, b];
+''');
+
+    final b_result = findElement.topVar('c').evaluationResult;
+    assertDartObjectText(b_result.value, r'''
+List
+  elementType: int
+  elements
+    int 0
+      variable: self::@variable::a
+    int 1
+    int 2
+      variable: self::@variable::b
+  variable: self::@variable::c
 ''');
   }
 
diff --git a/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart b/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
index 8124576b..ec37e39 100644
--- a/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
+++ b/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
@@ -2,20 +2,27 @@
 // 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 'dart:collection';
-
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/constant/value.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/summary2/reference.dart';
+import 'package:collection/collection.dart';
 
 /// Prints [DartObjectImpl] as a tree, with values and fields.
 class DartObjectPrinter {
   final StringBuffer sink;
+  final String selfUriStr;
 
-  DartObjectPrinter(this.sink);
+  String indent = '';
 
-  void write(DartObjectImpl? object, String indent) {
+  DartObjectPrinter({
+    required this.sink,
+    required this.selfUriStr,
+  });
+
+  void write(DartObjectImpl? object) {
     if (object != null) {
-      var type = object.type;
+      final type = object.type;
       if (object.isUnknown) {
         sink.write(
           type.getDisplayString(
@@ -33,34 +40,86 @@
         sink.write('String ');
         sink.writeln(object.toStringValue());
       } else if (type.isDartCoreList) {
-        var newIndent = '$indent  ';
+        type as InterfaceType;
         sink.writeln('List');
-        sink.write(newIndent);
-        sink.writeln(
-            'elementType: ${(type as InterfaceType).typeArguments[0]}');
-        var elements = object.toListValue()!;
-        for (int i = 0; i < elements.length; i++) {
-          sink.write(newIndent);
-          write(elements[i] as DartObjectImpl, newIndent);
-        }
-      } else if (object.isUserDefinedObject) {
-        var newIndent = '$indent  ';
-        var typeStr = type.getDisplayString(withNullability: true);
-        sink.writeln(typeStr);
-        var fields = object.fields;
-        if (fields != null) {
-          var sortedFields = SplayTreeMap.of(fields);
-          for (var entry in sortedFields.entries) {
-            sink.write(newIndent);
-            sink.write('${entry.key}: ');
-            write(entry.value, newIndent);
+        _withIndent(() {
+          _writelnWithIndent('elementType: ${type.typeArguments[0]}');
+          final elements = object.toListValue()!;
+          if (elements.isNotEmpty) {
+            _writelnWithIndent('elements');
+            _withIndent(() {
+              for (final element in elements) {
+                sink.write(indent);
+                write(element as DartObjectImpl);
+              }
+            });
           }
-        }
+        });
+      } else if (object.isUserDefinedObject) {
+        final typeStr = type.getDisplayString(withNullability: true);
+        sink.writeln(typeStr);
+        _withIndent(() {
+          final fields = object.fields;
+          if (fields != null) {
+            final sortedFields = fields.entries.sortedBy((e) => e.key);
+            for (final entry in sortedFields) {
+              sink.write(indent);
+              sink.write('${entry.key}: ');
+              write(entry.value);
+            }
+          }
+        });
       } else {
         throw UnimplementedError();
       }
+      _writeVariable(object);
     } else {
       sink.writeln('<null>');
     }
   }
+
+  String _referenceToString(Reference reference) {
+    final parent = reference.parent!;
+    if (parent.parent == null) {
+      final libraryUriStr = reference.name;
+      if (libraryUriStr == selfUriStr) {
+        return 'self';
+      }
+      return libraryUriStr;
+    }
+
+    // Ignore the unit, skip to the library.
+    if (parent.name == '@unit') {
+      return _referenceToString(parent.parent!);
+    }
+
+    var name = reference.name;
+    if (name.isEmpty) {
+      name = '•';
+    }
+    return '${_referenceToString(parent)}::$name';
+  }
+
+  void _withIndent(void Function() f) {
+    var savedIndent = indent;
+    indent = '$savedIndent  ';
+    f();
+    indent = savedIndent;
+  }
+
+  void _writelnWithIndent(String line) {
+    sink.write(indent);
+    sink.writeln(line);
+  }
+
+  void _writeVariable(DartObjectImpl object) {
+    final variable = object.variable;
+    if (variable is VariableElementImpl) {
+      _withIndent(() {
+        final reference = variable.reference!;
+        final referenceStr = _referenceToString(reference);
+        _writelnWithIndent('variable: $referenceStr');
+      });
+    }
+  }
 }
diff --git a/pkg/analyzer/test/src/dart/resolution/metadata_test.dart b/pkg/analyzer/test/src/dart/resolution/metadata_test.dart
index 8da2b07..08b5846 100644
--- a/pkg/analyzer/test/src/dart/resolution/metadata_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/metadata_test.dart
@@ -39,6 +39,7 @@
 ''');
     _assertAnnotationValueText(annotation, '''
 int 42
+  variable: self::@variable::a
 ''');
   }
 
@@ -67,6 +68,7 @@
     var annotationElement = annotation.elementAnnotation!;
     _assertElementAnnotationValueText(annotationElement, r'''
 int 42
+  variable: self::@variable::foo
 ''');
   }
 
@@ -102,6 +104,7 @@
     var annotationElement = annotation.elementAnnotation!;
     _assertElementAnnotationValueText(annotationElement, r'''
 int 42
+  variable: self::@variable::foo
 ''');
   }
 
@@ -128,6 +131,7 @@
 E
   _name: String v
   index: int 0
+  variable: self::@enum::E::@field::v
 ''');
   }
 
@@ -413,6 +417,7 @@
     _assertElementAnnotationValueText(
         findElement.function('f').metadata[0], r'''
 int 42
+  variable: package:test/a.dart::@class::A::@field::foo
 ''');
   }
 
@@ -446,6 +451,7 @@
     _assertElementAnnotationValueText(
         findElement.function('f').metadata[0], r'''
 int 42
+  variable: package:test/a.dart::@variable::foo
 ''');
   }
 
@@ -707,6 +713,7 @@
 ''');
     _assertAnnotationValueText(annotation, '''
 int 42
+  variable: self::@class::A::@field::foo
 ''');
   }
 
@@ -1678,6 +1685,7 @@
 ''');
     _assertAnnotationValueText(annotation, '''
 int 42
+  variable: package:test/a.dart::@class::A::@field::foo
 ''');
   }
 
@@ -1979,6 +1987,7 @@
 ''');
     _assertAnnotationValueText(annotation, '''
 int 42
+  variable: self::@class::A::@field::foo
 ''');
   }
 
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index d80f3d2..7ff2297 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -119,7 +119,10 @@
 
   void assertDartObjectText(DartObject? object, String expected) {
     var buffer = StringBuffer();
-    DartObjectPrinter(buffer).write(object as DartObjectImpl?, '');
+    DartObjectPrinter(
+      sink: buffer,
+      selfUriStr: '${result.libraryElement.source.uri}',
+    ).write(object as DartObjectImpl?);
     var actual = buffer.toString();
     if (actual != expected) {
       print(buffer);