[analyzer] Dot shorthands: Update and add tests for const dot shorthands.

Updated the AST binary reader/writer to handle dot shorthand invocations otherwise we crash when we try to evaluate a method invocation. Added a test for that in `const_test.dart`.

Added a plethora of tests in `evaluation_test.dart` which led me to find a bug with not carrying over the `isDotShorthand` flag when we convert from a method invocation to a constructor invocation.

Unit tests passing.

Bug: https://github.com/dart-lang/sdk/issues/59835
Change-Id: Ide1cb7cffc0285ed7d54e9f7bc641d0d28c1d2e2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437881
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index de1a413..0051cc0 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -110,7 +110,7 @@
 // TODO(scheglov): Clean up the list of implicitly analyzed files.
 class AnalysisDriver {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 487;
+  static const int DATA_VERSION = 488;
 
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index 2fbde83..9785b48 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -7197,8 +7197,17 @@
 
 base mixin DotShorthandMixin on ExpressionImpl {
   /// Whether the AST node is a dot shorthand and has a dot shorthand head
-  /// ([DotShorthandInvocation] or [DotShorthandPropertyAccess]) as its
+  /// ([DotShorthandInvocation], [DotShorthandConstructorInvocation] or
+  /// [DotShorthandPropertyAccess]) as its
   /// inner-most target.
+  ///
+  /// This is `false` and remains `false` when there is no dot shorthand head as
+  /// its inner-most target. When we are parsing and notice that we have a dot
+  /// shorthand head, we flip this flag to `true` and it remains `true` for that
+  /// expression.
+  ///
+  /// We use this flag to determine the correct context type to cache. This
+  /// cached context type is then used to resolve the dot shorthand head.
   bool isDotShorthand = false;
 }
 
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index 9aa2225..576c8d2 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -921,6 +921,11 @@
   }
 
   @override
+  Constant visitDotShorthandInvocation(DotShorthandInvocation node) {
+    return _invalidConstantForMethodInvocation(node);
+  }
+
+  @override
   Constant visitDotShorthandPropertyAccess(
     covariant DotShorthandPropertyAccessImpl node,
   ) {
@@ -1150,20 +1155,7 @@
       }
     }
 
-    // Some methods aren't resolved by the time we are evaluating it. We'll mark
-    // it and return immediately.
-    if (node.staticType is InvalidType) {
-      return InvalidConstant.forEntity(
-        entity: node,
-        diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
-        isUnresolved: true,
-      );
-    }
-
-    return InvalidConstant.forEntity(
-      entity: node,
-      diagnosticCode: CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION,
-    );
+    return _invalidConstantForMethodInvocation(node);
   }
 
   @override
@@ -2221,6 +2213,25 @@
     return value;
   }
 
+  // Common invalid constants for method invocations and dot shorthand
+  // invocations.
+  Constant _invalidConstantForMethodInvocation(Expression node) {
+    // Some methods aren't resolved by the time we are evaluating it. We'll mark
+    // it and return immediately.
+    if (node.staticType is InvalidType) {
+      return InvalidConstant.forEntity(
+        entity: node,
+        diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
+        isUnresolved: true,
+      );
+    }
+
+    return InvalidConstant.forEntity(
+      entity: node,
+      diagnosticCode: CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION,
+    );
+  }
+
   /// Returns the first not-potentially constant error found with [node] or
   /// `null` if there are none.
   InvalidConstant? _reportNotPotentialConstants(AstNode node) {
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index 644ae4d..27e93d0 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -1288,13 +1288,16 @@
     // [DotShorthandConstructorInvocation].
     if (receiver.getNamedConstructor2(name) case ConstructorElementImpl element?
         when element.isAccessibleIn2(_resolver.definingLibrary)) {
-      var replacement = DotShorthandConstructorInvocationImpl(
-        constKeyword: null,
-        period: node.period,
-        constructorName: nameNode,
-        typeArguments: node.typeArguments,
-        argumentList: node.argumentList,
-      )..element = element;
+      var replacement =
+          DotShorthandConstructorInvocationImpl(
+              constKeyword: null,
+              period: node.period,
+              constructorName: nameNode,
+              typeArguments: node.typeArguments,
+              argumentList: node.argumentList,
+            )
+            ..element = element
+            ..isDotShorthand = node.isDotShorthand;
       _resolver.replaceExpression(node, replacement);
       _resolver.flowAnalysis.transferTestData(node, replacement);
       return replacement;
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
index 007151f..66576ff 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -65,6 +65,8 @@
         return _readDefaultFormalParameter();
       case Tag.DotShorthandConstructorInvocation:
         return _readDotShorthandConstructorInvocation();
+      case Tag.DotShorthandInvocation:
+        return _readDotShorthandInvocation();
       case Tag.DotShorthandPropertyAccess:
         return _readDotShorthandPropertyAccess();
       case Tag.DottedName:
@@ -482,6 +484,20 @@
     return node;
   }
 
+  DotShorthandInvocation _readDotShorthandInvocation() {
+    var memberName = readNode() as SimpleIdentifierImpl;
+    var typeArguments = _readOptionalNode() as TypeArgumentListImpl?;
+    var arguments = readNode() as ArgumentListImpl;
+    var node = DotShorthandInvocationImpl(
+      period: Tokens.period(),
+      memberName: memberName,
+      typeArguments: typeArguments,
+      argumentList: arguments,
+    );
+    _readInvocationExpression(node);
+    return node;
+  }
+
   DotShorthandPropertyAccess _readDotShorthandPropertyAccess() {
     var propertyName = readNode() as SimpleIdentifierImpl;
     var node = DotShorthandPropertyAccessImpl(
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
index 0d90ee3..be52c2a 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_tag.dart
@@ -52,6 +52,7 @@
   static const int DeclaredIdentifier = 90;
   static const int DefaultFormalParameter = 8;
   static const int DotShorthandConstructorInvocation = 114;
+  static const int DotShorthandInvocation = 115;
   static const int DotShorthandPropertyAccess = 113;
   static const int DottedName = 47;
   static const int DoubleLiteral = 9;
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
index c093a9a..d6418e3 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_writer.dart
@@ -240,6 +240,13 @@
   }
 
   @override
+  void visitDotShorthandInvocation(DotShorthandInvocation node) {
+    _writeByte(Tag.DotShorthandInvocation);
+    _writeNode(node.memberName);
+    _storeInvocationExpression(node);
+  }
+
+  @override
   void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
     _writeByte(Tag.DotShorthandPropertyAccess);
     _writeNode(node.propertyName);
diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
index f996ad7..1c7747c 100644
--- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
+++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
@@ -94,6 +94,171 @@
     );
   }
 
+  test_dotShorthand_enum_simple() async {
+    await resolveTestCode('''
+enum E { v1, v2 }
+const E x1 = .v1;
+const E x2 = .v2;
+''');
+    assertDartObjectText(_topLevelVar('x1'), r'''
+E
+  _name: String v1
+  index: int 0
+  variable: <testLibrary>::@topLevelVariable::x1
+''');
+    assertDartObjectText(_topLevelVar('x2'), r'''
+E
+  _name: String v2
+  index: int 1
+  variable: <testLibrary>::@topLevelVariable::x2
+''');
+  }
+
+  test_dotShorthand_equalEqual_constructor() async {
+    await assertNoErrorsInCode('''
+class A {
+  const A();
+}
+
+const v = A() == .new();
+''');
+    var result = _topLevelVar('v');
+    assertDartObjectText(result, '''
+bool true
+  variable: <testLibrary>::@topLevelVariable::v
+''');
+  }
+
+  test_dotShorthand_equalEqual_constructor_lhsShorthand() async {
+    await assertErrorsInCode(
+      '''
+class A {
+  const A();
+}
+
+const v = .new() == A();
+''',
+      [
+        error(
+          CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE,
+          36,
+          6,
+        ),
+        error(CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION, 37, 3),
+      ],
+    );
+  }
+
+  test_dotShorthand_equalEqual_field() async {
+    await assertNoErrorsInCode('''
+class A {
+  const A();
+  static const A field = A();
+}
+
+const v = A() == .field;
+''');
+    var result = _topLevelVar('v');
+    assertDartObjectText(result, '''
+bool true
+  variable: <testLibrary>::@topLevelVariable::v
+''');
+  }
+
+  test_dotShorthand_equalEqual_method_error() async {
+    await assertErrorsInCode(
+      '''
+class A {
+  static A method() => A();
+}
+
+const v = A() == .method();
+''',
+      [
+        error(CompileTimeErrorCode.CONST_WITH_NON_CONST, 51, 3),
+        error(
+          CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE,
+          51,
+          3,
+        ),
+      ],
+    );
+  }
+
+  test_dotShorthand_method_invalid() async {
+    await assertErrorsInCode(
+      '''
+class A {
+  static A method() => A();
+}
+const A a = .method();
+''',
+      [error(CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION, 52, 9)],
+    );
+  }
+
+  test_dotShorthand_missingContext_invocation() async {
+    await assertErrorsInCode(
+      '''
+const a = .new();
+''',
+      [
+        error(
+          CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE,
+          10,
+          6,
+        ),
+        error(CompileTimeErrorCode.DOT_SHORTHAND_UNDEFINED_INVOCATION, 11, 3),
+      ],
+    );
+  }
+
+  test_dotShorthand_missingContext_propertyAccess() async {
+    await assertErrorsInCode(
+      '''
+const a = .id;
+''',
+      [
+        error(CompileTimeErrorCode.DOT_SHORTHAND_MISSING_CONTEXT, 10, 3),
+        error(
+          CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE,
+          10,
+          3,
+        ),
+      ],
+    );
+  }
+
+  test_dotShorthand_propertyAccess() async {
+    await assertNoErrorsInCode('''
+class A {
+  const A();
+  static const A field = A();
+}
+
+const A a = .field;
+''');
+    var result = _topLevelVar('a');
+    assertDartObjectText(result, '''
+A
+  variable: <testLibrary>::@topLevelVariable::a
+''');
+  }
+
+  test_dotShorthand_propertyAccess_enum() async {
+    await assertNoErrorsInCode('''
+enum E { a }
+const E e = .a;
+''');
+    var result = _topLevelVar('e');
+    assertDartObjectText(result, r'''
+E
+  _name: String a
+  index: int 0
+  variable: <testLibrary>::@topLevelVariable::e
+''');
+  }
+
   test_enum_argument_methodInvocation() async {
     await assertErrorsInCode(
       '''
@@ -6108,6 +6273,162 @@
     );
   }
 
+  test_dotShorthand_assertInitializer_assertIsNot_false() async {
+    await assertErrorsInCode(
+      '''
+class A {
+  const A() : assert(0 is! int);
+}
+
+const A a = .new();
+''',
+      [
+        error(WarningCode.UNNECESSARY_TYPE_CHECK_FALSE, 31, 9),
+        error(
+          CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+          58,
+          6,
+          contextMessages: [
+            ExpectedContextMessage(
+              testFile,
+              24,
+              17,
+              text:
+                  "The exception is 'The assertion in this constant expression failed.' and occurs here.",
+            ),
+          ],
+        ),
+      ],
+    );
+  }
+
+  test_dotShorthand_assertInitializer_assertIsNot_true() async {
+    await assertNoErrorsInCode('''
+class A {
+  const A() : assert(0 is! String);
+}
+
+const A a = .new();
+''');
+    var result = _topLevelVar('a');
+    assertDartObjectText(result, '''
+A
+  variable: <testLibrary>::@topLevelVariable::a
+''');
+  }
+
+  test_dotShorthand_assertInitializer_enum_false() async {
+    await assertErrorsInCode(
+      '''
+enum E { a, b }
+class A {
+  const A(E e) : assert(e != .a);
+}
+const A a = .new(.a);
+''',
+      [
+        error(
+          CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+          74,
+          8,
+          contextMessages: [
+            ExpectedContextMessage(
+              testFile,
+              43,
+              15,
+              text:
+                  "The exception is 'The assertion in this constant expression failed.' and occurs here.",
+            ),
+          ],
+        ),
+      ],
+    );
+  }
+
+  test_dotShorthand_assertInitializer_enum_true() async {
+    await assertNoErrorsInCode('''
+enum E { a, b }
+class A {
+  const A(E e) : assert(e != .a);
+}
+const A a = .new(.b);
+''');
+    var result = _topLevelVar('a');
+    assertDartObjectText(result, '''
+A
+  variable: <testLibrary>::@topLevelVariable::a
+''');
+  }
+
+  test_dotShorthand_assertInitializer_simpleInSuperInitializer_true() async {
+    await assertErrorsInCode(
+      '''
+class A {
+  const A(): assert(1 is int);
+}
+class B extends A {
+  const B() : super();
+}
+const B b = .new();
+''',
+      [error(WarningCode.UNNECESSARY_TYPE_CHECK_TRUE, 30, 8)],
+    );
+    var result = _topLevelVar('b');
+    assertDartObjectText(result, '''
+B
+  (super): A
+  variable: <testLibrary>::@topLevelVariable::b
+''');
+  }
+
+  test_dotShorthand_bool_fromEnvironment() async {
+    await assertNoErrorsInCode('''
+const bool a = .fromEnvironment('a');
+const bool b = .fromEnvironment('b', defaultValue: true);
+''');
+    assertDartObjectText(_topLevelVar('a'), '''
+bool false
+  variable: <testLibrary>::@topLevelVariable::a
+''');
+    assertDartObjectText(
+      _evaluateConstant('a', declaredVariables: {'a': 'true'}),
+      '''
+bool true
+''',
+    );
+
+    var bResult = _evaluateConstant(
+      'b',
+      declaredVariables: {'b': 'bbb'},
+      lexicalEnvironment: {
+        'defaultValue': DartObjectImpl(
+          typeSystem,
+          typeProvider.boolType,
+          BoolState(true),
+        ),
+      },
+    );
+    assertDartObjectText(bResult, '''
+bool true
+''');
+  }
+
+  test_dotShorthand_bool_hasEnvironment() async {
+    await assertNoErrorsInCode('''
+const bool a = .hasEnvironment('a');
+''');
+    assertDartObjectText(_topLevelVar('a'), '''
+bool false
+  variable: <testLibrary>::@topLevelVariable::a
+''');
+    assertDartObjectText(
+      _evaluateConstant('a', declaredVariables: {'a': '42'}),
+      '''
+bool true
+''',
+    );
+  }
+
   test_dotShorthand_constantArgument_issue60963() async {
     await assertNoErrorsInCode('''
 class A {
diff --git a/pkg/analyzer/test/src/summary/elements/const_test.dart b/pkg/analyzer/test/src/summary/elements/const_test.dart
index aaf2a87..5e5396b 100644
--- a/pkg/analyzer/test/src/summary/elements/const_test.dart
+++ b/pkg/analyzer/test/src/summary/elements/const_test.dart
@@ -725,6 +725,80 @@
 ''');
   }
 
+  test_const_dotShorthand_invalid_methodInvocation() async {
+    var library = await buildLibrary(r'''
+class A {
+  static A method() => A();
+}
+
+const A a = .method();
+''');
+    checkElementText(library, r'''
+library
+  reference: <testLibrary>
+  fragments
+    #F0 <testLibraryFragment>
+      element: <testLibrary>
+      classes
+        #F1 class A @6
+          element: <testLibrary>::@class::A
+          constructors
+            #F2 synthetic new
+              element: <testLibrary>::@class::A::@constructor::new
+              typeName: A
+          methods
+            #F3 method @21
+              element: <testLibrary>::@class::A::@method::method
+      topLevelVariables
+        #F4 hasInitializer a @49
+          element: <testLibrary>::@topLevelVariable::a
+          initializer: expression_0
+            DotShorthandInvocation
+              period: . @53
+              memberName: SimpleIdentifier
+                token: method @54
+                element: <testLibrary>::@class::A::@method::method
+                staticType: A Function()
+              argumentList: ArgumentList
+                leftParenthesis: ( @60
+                rightParenthesis: ) @61
+              staticInvokeType: A Function()
+              staticType: A
+      getters
+        #F5 synthetic a
+          element: <testLibrary>::@getter::a
+          returnType: A
+  classes
+    class A
+      reference: <testLibrary>::@class::A
+      firstFragment: #F1
+      constructors
+        synthetic new
+          reference: <testLibrary>::@class::A::@constructor::new
+          firstFragment: #F2
+      methods
+        static method
+          reference: <testLibrary>::@class::A::@method::method
+          firstFragment: #F3
+          returnType: A
+  topLevelVariables
+    const hasInitializer a
+      reference: <testLibrary>::@topLevelVariable::a
+      firstFragment: #F4
+      type: A
+      constantInitializer
+        fragment: #F4
+        expression: expression_0
+      getter: <testLibrary>::@getter::a
+  getters
+    synthetic static a
+      reference: <testLibrary>::@getter::a
+      firstFragment: #F5
+      returnType: A
+      variable: <testLibrary>::@topLevelVariable::a
+''');
+  }
+
   test_const_dotShorthand_property() async {
     var library = await buildLibrary(r'''
 class A {