Add a Hint enforcing contract of @literal

Example code:

```dart
import 'package:meta/meta.dart';

class A {
  @literal
  const A();

  @literal
  const A.named();
}

var a = A();
var b = const A();
const c = A();
var d = new A();

var e = new A.named();
```

Produces:

Analyzing b.dart...
  hint • This instance creation must be 'const', because the A constructor is marked as '@literal' at b.dart:11:9 • non_const_call_to_literal_constructor
  hint • This instance creation must be 'const', because the A constructor is marked as '@literal' at b.dart:14:9 • non_const_call_to_literal_constructor
  hint • This instance creation must be 'const', because the A.named constructor is marked as '@literal' at b.dart:16:9 • non_const_call_to_literal_constructor
3 hints found.

Bug: https://github.com/dart-lang/sdk/issues/34259
Change-Id: Iba10e5e0a0b2d0f7e99556cd6c201cea229ad675
Reviewed-on: https://dart-review.googlesource.com/c/88422
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart
index 547cb82..0260c07 100644
--- a/pkg/analyzer/lib/dart/element/element.dart
+++ b/pkg/analyzer/lib/dart/element/element.dart
@@ -534,6 +534,9 @@
   /// Return `true` if this element has an annotation of the form `@JS(..)`.
   bool get hasJS;
 
+  /// Return `true` if this element has an annotation of the form '@literal'.
+  bool get hasLiteral;
+
   /// Return `true` if this element has an annotation of the form `@override`.
   bool get hasOverride;
 
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 7ffa3f6..7d59012 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -310,6 +310,8 @@
   HintCode.MIXIN_ON_SEALED_CLASS,
   HintCode.MUST_BE_IMMUTABLE,
   HintCode.MUST_CALL_SUPER,
+  HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
+  HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW,
   HintCode.NULL_AWARE_BEFORE_OPERATOR,
   HintCode.NULL_AWARE_IN_CONDITION,
   HintCode.NULL_AWARE_IN_LOGICAL_OPERATOR,
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index f23c65a..77eabab 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -2836,6 +2836,18 @@
   }
 
   @override
+  bool get hasLiteral {
+    var metadata = this.metadata;
+    for (var i = 0; i < metadata.length; i++) {
+      var annotation = metadata[i];
+      if (annotation.isLiteral) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
   bool get hasOverride {
     var metadata = this.metadata;
     for (var i = 0; i < metadata.length; i++) {
@@ -6400,6 +6412,9 @@
   bool get hasJS => false;
 
   @override
+  bool get hasLiteral => false;
+
+  @override
   bool get hasOverride => false;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/element/handle.dart b/pkg/analyzer/lib/src/dart/element/handle.dart
index 97f978f..67f47f7 100644
--- a/pkg/analyzer/lib/src/dart/element/handle.dart
+++ b/pkg/analyzer/lib/src/dart/element/handle.dart
@@ -46,6 +46,9 @@
   bool get hasJS => actualElement.hasJS;
 
   @override
+  bool get hasLiteral => actualElement.hasLiteral;
+
+  @override
   bool get hasNonFinalField => actualElement.hasNonFinalField;
 
   @override
@@ -389,6 +392,9 @@
   bool get hasJS => actualElement.hasJS;
 
   @override
+  bool get hasLiteral => actualElement.hasLiteral;
+
+  @override
   bool get hasOverride => actualElement.hasOverride;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/element/member.dart b/pkg/analyzer/lib/src/dart/element/member.dart
index bc18d7b7..484458a 100644
--- a/pkg/analyzer/lib/src/dart/element/member.dart
+++ b/pkg/analyzer/lib/src/dart/element/member.dart
@@ -422,6 +422,9 @@
   bool get hasJS => _baseElement.hasJS;
 
   @override
+  bool get hasLiteral => _baseElement.hasLiteral;
+
+  @override
   bool get hasOverride => _baseElement.hasOverride;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/element/wrapped.dart b/pkg/analyzer/lib/src/dart/element/wrapped.dart
index 3dfc8d6..3c6f7f1 100644
--- a/pkg/analyzer/lib/src/dart/element/wrapped.dart
+++ b/pkg/analyzer/lib/src/dart/element/wrapped.dart
@@ -65,6 +65,9 @@
   bool get hasJS => wrappedUnit.hasJS;
 
   @override
+  bool get hasLiteral => wrappedUnit.hasLiteral;
+
+  @override
   bool get hasLoadLibraryFunction => wrappedUnit.hasLoadLibraryFunction;
 
   @override
@@ -255,6 +258,9 @@
   bool get hasJS => wrappedImport.hasJS;
 
   @override
+  bool get hasLiteral => wrappedImport.hasLiteral;
+
+  @override
   bool get hasOverride => wrappedImport.hasOverride;
 
   @override
@@ -454,6 +460,9 @@
   bool get hasJS => wrappedLib.hasJS;
 
   @override
+  bool get hasLiteral => wrappedLib.hasLiteral;
+
+  @override
   bool get hasLoadLibraryFunction => wrappedLib.hasLoadLibraryFunction;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
index 07700ec..76fe3c2 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
@@ -399,6 +399,27 @@
       "but does not invoke the overridden method.");
 
   /**
+   * Generate a hint for non-const instance creation using a constructor
+   * annotated with `@literal`.
+   */
+  static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR = const HintCode(
+      'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR',
+      "This instance creation must be 'const', because the {0} constructor is "
+      "marked as '@literal'.",
+      correction: "Try adding a 'const' keyword.");
+
+  /**
+   * Generate a hint for non-const instance creation (with the `new` keyword)
+   * using a constructor annotated with `@literal`.
+   */
+  static const HintCode NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW =
+      const HintCode(
+          'NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW',
+          "This instance creation must be 'const', because the {0} constructor is "
+          "marked as '@literal'.",
+          correction: "Try replacing the 'new' keyword with 'const'.");
+
+  /**
    * When the left operand of a binary expression uses '?.' operator, it can be
    * `null`.
    */
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 3696609..f239508 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -34,6 +34,7 @@
 import 'package:analyzer/src/generated/static_type_analyzer.dart';
 import 'package:analyzer/src/generated/testing/element_factory.dart';
 import 'package:analyzer/src/generated/type_system.dart';
+import 'package:analyzer/src/lint/linter.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:path/path.dart' as path;
 
@@ -286,6 +287,9 @@
   /// The [WorkspacePackage] in which [_currentLibrary] is declared.
   WorkspacePackage _workspacePackage;
 
+  /// The [LinterContext] used for possible const calculations.
+  LinterContext _linterContext;
+
   /// Create a new instance of the [BestPracticesVerifier].
   ///
   /// @param errorReporter the error reporter
@@ -295,6 +299,7 @@
     this._currentLibrary, {
     TypeSystem typeSystem,
     ResourceProvider resourceProvider,
+    DeclaredVariables declaredVariables,
   })  : _nullType = typeProvider.nullType,
         _futureNullType = typeProvider.futureNullType,
         _typeSystem = typeSystem ?? new Dart2TypeSystem(typeProvider),
@@ -305,6 +310,8 @@
     Workspace workspace = ContextBuilder.createWorkspace(
         resourceProvider, libraryPath, null /* ContextBuilder */);
     _workspacePackage = workspace.findPackageFor(libraryPath);
+    _linterContext = LinterContextImpl(null /* allUnits */,
+        null /* currentUnit */, declaredVariables, typeProvider, _typeSystem);
   }
 
   @override
@@ -480,6 +487,7 @@
   @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     _checkForDeprecatedMemberUse(node.staticElement, node);
+    _checkForLiteralConstructorUse(node);
     super.visitInstanceCreationExpression(node);
   }
 
@@ -1003,6 +1011,32 @@
     return false;
   }
 
+  /// Check that the instance creation node is const if the constructor is
+  /// marked with [literal].
+  _checkForLiteralConstructorUse(InstanceCreationExpression node) {
+    ConstructorName constructorName = node.constructorName;
+    ConstructorElement constructor = constructorName.staticElement;
+    if (constructor == null) {
+      return;
+    }
+    if (!node.isConst &&
+        constructor.hasLiteral &&
+        _linterContext.canBeConst(node)) {
+      // Echoing jwren's TODO from _checkForDeprecatedMemberUse:
+      // TODO(jwren) We should modify ConstructorElement.getDisplayName(), or
+      // have the logic centralized elsewhere, instead of doing this logic
+      // here.
+      String fullConstructorName = constructorName.type.name.name;
+      if (constructorName.name != null) {
+        fullConstructorName = '$fullConstructorName.${constructorName.name}';
+      }
+      HintCode hint = node.keyword?.keyword == Keyword.NEW
+          ? HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW
+          : HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
+      _errorReporter.reportErrorForNode(hint, node, [fullConstructorName]);
+    }
+  }
+
   /// Generate a hint for functions or methods that have a return type, but do
   /// not have a return statement on all branches. At the end of blocks with no
   /// return, Dart implicitly returns `null`, avoiding these implicit returns is
diff --git a/pkg/analyzer/test/generated/hint_code_test.dart b/pkg/analyzer/test/generated/hint_code_test.dart
index 0a7da26..d537953 100644
--- a/pkg/analyzer/test/generated/hint_code_test.dart
+++ b/pkg/analyzer/test/generated/hint_code_test.dart
@@ -1323,6 +1323,55 @@
     verify([source]);
   }
 
+  test_nonConstCallToLiteralConstructor_nonConstContext() async {
+    Source source = addSource(r'''
+import 'package:meta/meta.dart';
+class A {
+  @literal
+  const A();
+}
+void main() {
+  var a = A();
+}
+''');
+    await computeAnalysisResult(source);
+    assertErrors(source, [HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR]);
+    verify([source]);
+  }
+
+  test_nonConstCallToLiteralConstructor_usingNew() async {
+    Source source = addSource(r'''
+import 'package:meta/meta.dart';
+class A {
+  @literal
+  const A();
+}
+void main() {
+  var a = new A();
+}
+''');
+    await computeAnalysisResult(source);
+    assertErrors(
+        source, [HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW]);
+    verify([source]);
+  }
+
+  test_nonConstCallToLiteralConstructor_namedConstructor() async {
+    Source source = addSource(r'''
+import 'package:meta/meta.dart';
+class A {
+  @literal
+  const A.named();
+}
+void main() {
+  var a = A.named();
+}
+''');
+    await computeAnalysisResult(source);
+    assertErrors(source, [HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR]);
+    verify([source]);
+  }
+
   test_invalidLiteralAnnotation_nonConstructor() async {
     Source source = addSource(r'''
 import 'package:meta/meta.dart';
diff --git a/pkg/analyzer/test/generated/non_hint_code_test.dart b/pkg/analyzer/test/generated/non_hint_code_test.dart
index f1af9c5..26c4abc 100644
--- a/pkg/analyzer/test/generated/non_hint_code_test.dart
+++ b/pkg/analyzer/test/generated/non_hint_code_test.dart
@@ -483,6 +483,57 @@
     verify([source]);
   }
 
+  test_nonConstCallToLiteralConstructor_constCreation() async {
+    Source source = addSource(r'''
+import 'package:meta/meta.dart';
+class A {
+  @literal
+  const A();
+}
+
+void main() {
+  const a = const A();
+}
+''');
+    await computeAnalysisResult(source);
+    assertNoErrors(source);
+    verify([source]);
+  }
+
+  test_nonConstCallToLiteralConstructor_constContextCreation() async {
+    Source source = addSource(r'''
+import 'package:meta/meta.dart';
+class A {
+  @literal
+  const A();
+}
+
+void main() {
+  const a = A();
+}
+''');
+    await computeAnalysisResult(source);
+    assertNoErrors(source);
+    verify([source]);
+  }
+
+  test_nonConstCallToLiteralConstructor_unconstableCreation() async {
+    Source source = addSource(r'''
+import 'package:meta/meta.dart';
+class A {
+  @literal
+  const A(List list);
+}
+
+void main() {
+  var a = A(new List());
+}
+''');
+    await computeAnalysisResult(source);
+    assertNoErrors(source);
+    verify([source]);
+  }
+
   test_overrideOnNonOverridingField_inInterface() async {
     Source source = addSource(r'''
 class A {