[cfe] Dot shorthands - Const constructors

Parse and build dot shorthand invocations that are constant.
Added a new listener to handle and store the const-ness. It didn't feel right re-using any of the other `beginConstPattern` methods.

Added an error message if invoking a non-const constructor where we expected a const constructor.

Bug: https://github.com/dart-lang/sdk/issues/59758
Change-Id: I8551e3b8f71e89a69d090510bb64694d5e09247d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/414660
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart b/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
index d95ce8c..59d81bb 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
@@ -2203,6 +2203,16 @@
   void handleDotShorthandContext(Token token) {
     listener?.handleDotShorthandContext(token);
   }
+
+  @override
+  void beginConstDotShorthand(Token token) {
+    listener?.beginConstDotShorthand(token);
+  }
+
+  @override
+  void endConstDotShorthand(Token token) {
+    listener?.beginConstDotShorthand(token);
+  }
 }
 
 class NullListener extends ForwardingListener {
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart b/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
index dc07b8b..187726a 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
@@ -2400,4 +2400,10 @@
   void handleDotShorthandHead(Token token) {
     logEvent('DotShorthandHead');
   }
+
+  void beginConstDotShorthand(Token token) {}
+
+  void endConstDotShorthand(Token token) {
+    logEvent('ConstDotShorthand');
+  }
 }
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
index b0eeba6..7654aeb 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
@@ -5826,13 +5826,24 @@
     return token;
   }
 
+  /// Returns `true` if [period] is a `.` and the next token after is an
+  /// identifier or the `new` keyword.
+  ///
+  /// This indicates the parsing of a dot shorthand e.g. `.parse(42)`.
+  bool _isDotShorthand(Token period) {
+    if (period.isA(TokenType.PERIOD) &&
+        (period.next!.isIdentifier || period.next!.isA(Keyword.NEW))) {
+      return true;
+    }
+    return false;
+  }
+
   Token parsePrecedenceExpression(Token token, int precedence,
       bool allowCascades, ConstantPatternContext constantPatternContext) {
     assert(precedence >= 1);
     assert(precedence <= SELECTOR_PRECEDENCE);
 
-    bool isDotShorthand = token.next!.isA(TokenType.PERIOD) &&
-        (token.next!.next!.isIdentifier || token.next!.next!.isA(Keyword.NEW));
+    bool isDotShorthand = _isDotShorthand(token.next!);
     if (isDotShorthand) {
       // TODO(kallentu): Once the analyzer implementation is done, we can avoid
       // adding a synthetic identifier completely, but currently, the parser
@@ -7463,6 +7474,19 @@
         assert(false, "Expected either [, [] or < but found neither.");
       }
     }
+
+    bool isDotShorthand = _isDotShorthand(token.next!);
+    if (isDotShorthand) {
+      Token dot = token.next!;
+      listener.beginConstDotShorthand(constKeyword);
+      token = parsePrimary(dot, IdentifierContext.expressionContinuation,
+          ConstantPatternContext.explicit);
+      listener.handleDotShorthandHead(dot);
+      listener.handleDotShorthandContext(dot);
+      listener.endConstDotShorthand(constKeyword);
+      return token;
+    }
+
     listener.beginConstExpression(constKeyword);
     token = parseConstructorReference(token, ConstructorReferenceContext.Const,
         /* typeArg = */ potentialTypeArg);
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index 65bf042..60032f2 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -1494,6 +1494,19 @@
   }
 
   @override
+  void endConstDotShorthand(Token token) {
+    debugEvent("endConstDotShorthand");
+    if (!enabledDotShorthands) {
+      _reportFeatureNotEnabled(
+        feature: ExperimentalFeatures.dot_shorthands,
+        startToken: token,
+      );
+    }
+
+    // TODO(kallentu): Handle dot shorthands.
+  }
+
+  @override
   void endConstExpression(Token constKeyword) {
     assert(optional('const', constKeyword));
     debugEvent("ConstExpression");
diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart
index 4c1ef40..a49b8f9 100644
--- a/pkg/front_end/lib/src/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/kernel/body_builder.dart
@@ -6413,6 +6413,7 @@
       push(
           new ParserErrorGenerator(this, nameToken, cfe.messageSyntheticToken));
     } else if (type is InvalidExpression) {
+      // Coverage-ignore-block(suite): Not run.
       push(type);
     } else if (type is Expression) {
       push(createInstantiationAndInvocation(
@@ -9982,7 +9983,6 @@
   void handleDotShorthandContext(Token token) {
     debugEvent("DotShorthandContext");
     if (!libraryFeatures.dotShorthands.isEnabled) {
-      // Coverage-ignore-block(suite): Not run.
       addProblem(
           templateExperimentNotEnabledOffByDefault
               .withArguments(ExperimentalFlag.dotShorthands.name),
@@ -10005,7 +10005,6 @@
   void handleDotShorthandHead(Token token) {
     debugEvent("DotShorthandHead");
     if (!libraryFeatures.dotShorthands.isEnabled) {
-      // Coverage-ignore-block(suite): Not run.
       addProblem(
           templateExperimentNotEnabledOffByDefault
               .withArguments(ExperimentalFlag.dotShorthands.name),
@@ -10024,7 +10023,9 @@
       if (selector is InvocationSelector) {
         // e.g. `.parse(2)`
         push(forest.createDotShorthandInvocation(
-            offsetForToken(token), selector.name, selector.arguments));
+            offsetForToken(token), selector.name, selector.arguments,
+            nameOffset: offsetForToken(token.next),
+            isConst: constantContext == ConstantContext.inferred));
       } else if (selector is PropertySelector) {
         // e.g. `.zero`
         push(forest.createDotShorthandPropertyGet(
@@ -10032,6 +10033,21 @@
       }
     }
   }
+
+  @override
+  void beginConstDotShorthand(Token token) {
+    debugEvent("beginConstDotShorthand");
+    super.push(constantContext);
+    constantContext = ConstantContext.inferred;
+  }
+
+  @override
+  void endConstDotShorthand(Token token) {
+    debugEvent("endConstDotShorthand");
+    Object? dotShorthand = pop();
+    constantContext = pop() as ConstantContext;
+    push(dotShorthand);
+  }
 }
 
 class Operator {
diff --git a/pkg/front_end/lib/src/kernel/expression_generator.dart b/pkg/front_end/lib/src/kernel/expression_generator.dart
index 3d80736..9b72050 100644
--- a/pkg/front_end/lib/src/kernel/expression_generator.dart
+++ b/pkg/front_end/lib/src/kernel/expression_generator.dart
@@ -4440,6 +4440,7 @@
   }
 
   @override
+  // Coverage-ignore(suite): Not run.
   Expression qualifiedLookup(Token name) {
     return buildProblem();
   }
diff --git a/pkg/front_end/lib/src/kernel/forest.dart b/pkg/front_end/lib/src/kernel/forest.dart
index 4c92229..9457db0 100644
--- a/pkg/front_end/lib/src/kernel/forest.dart
+++ b/pkg/front_end/lib/src/kernel/forest.dart
@@ -934,8 +934,11 @@
   }
 
   DotShorthandInvocation createDotShorthandInvocation(
-      int fileOffset, Name name, Arguments arguments) {
-    return new DotShorthandInvocation(name, arguments)..fileOffset = fileOffset;
+      int fileOffset, Name name, Arguments arguments,
+      {required int nameOffset, required bool isConst}) {
+    return new DotShorthandInvocation(name, arguments, nameOffset,
+        isConst: isConst)
+      ..fileOffset = fileOffset;
   }
 
   DotShorthandPropertyGet createDotShorthandPropertyGet(
diff --git a/pkg/front_end/lib/src/kernel/internal_ast.dart b/pkg/front_end/lib/src/kernel/internal_ast.dart
index db71ecf..8ef75a3 100644
--- a/pkg/front_end/lib/src/kernel/internal_ast.dart
+++ b/pkg/front_end/lib/src/kernel/internal_ast.dart
@@ -3254,11 +3254,13 @@
 /// This node could represent a shorthand of a static method or a named
 /// constructor.
 class DotShorthandInvocation extends InternalExpression {
-  Name name;
+  final Name name;
+  final int nameOffset;
+  final Arguments arguments;
+  final bool isConst;
 
-  Arguments arguments;
-
-  DotShorthandInvocation(this.name, this.arguments);
+  DotShorthandInvocation(this.name, this.arguments, this.nameOffset,
+      {required this.isConst});
 
   @override
   ExpressionInferenceResult acceptInference(
@@ -3274,6 +3276,9 @@
   @override
   // Coverage-ignore(suite): Not run.
   void toTextInternal(AstPrinter printer) {
+    if (isConst) {
+      printer.write('const ');
+    }
     printer.write('.');
     printer.writeName(name);
     printer.writeArguments(arguments);
diff --git a/pkg/front_end/lib/src/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/type_inference/inference_visitor.dart
index c61e882..ec7a130 100644
--- a/pkg/front_end/lib/src/type_inference/inference_visitor.dart
+++ b/pkg/front_end/lib/src/type_inference/inference_visitor.dart
@@ -12151,15 +12151,40 @@
       Member? constructor =
           findConstructor(cachedContext, node.name, node.fileOffset);
       if (constructor is Constructor) {
-        // TODO(kallentu): Const constructors.
+        if (!constructor.isConst && node.isConst) {
+          Expression replacement = helper.buildProblem(
+              messageNonConstConstructor,
+              node.nameOffset,
+              node.name.text.length);
+          return new ExpressionInferenceResult(
+              const DynamicType(), replacement);
+        }
+
         expr = new ConstructorInvocation(constructor, node.arguments,
-            isConst: false)
+            isConst: node.isConst)
           ..fileOffset = node.fileOffset;
       } else if (constructor is Procedure) {
         // [constructor] can be a [Procedure] if we have an extension type
-        // constructor.
-        expr = new StaticInvocation(constructor, node.arguments)
-          ..fileOffset = node.fileOffset;
+        // constructor or a redirecting factory constructor.
+        if (!constructor.isConst && node.isConst) {
+          // Coverage-ignore-block(suite): Not run.
+          Expression replacement = helper.buildProblem(
+              messageNonConstConstructor,
+              node.nameOffset,
+              node.name.text.length);
+          return new ExpressionInferenceResult(
+              const DynamicType(), replacement);
+        }
+
+        if (constructor.isRedirectingFactory) {
+          expr = new FactoryConstructorInvocation(constructor, node.arguments,
+              isConst: node.isConst)
+            ..fileOffset = node.fileOffset;
+        } else {
+          expr = new StaticInvocation(constructor, node.arguments,
+              isConst: node.isConst)
+            ..fileOffset = node.fileOffset;
+        }
       } else {
         // Coverage-ignore-block(suite): Not run.
         // TODO(kallentu): This is temporary. Build a problem with an error
diff --git a/pkg/front_end/lib/src/util/parser_ast_helper.dart b/pkg/front_end/lib/src/util/parser_ast_helper.dart
index 598fa59..a5296cd 100644
--- a/pkg/front_end/lib/src/util/parser_ast_helper.dart
+++ b/pkg/front_end/lib/src/util/parser_ast_helper.dart
@@ -3216,6 +3216,20 @@
         new DotShorthandHeadHandle(ParserAstType.HANDLE, token: token);
     seen(data);
   }
+
+  @override
+  void beginConstDotShorthand(Token token) {
+    ConstDotShorthandBegin data =
+        new ConstDotShorthandBegin(ParserAstType.BEGIN, token: token);
+    seen(data);
+  }
+
+  @override
+  void endConstDotShorthand(Token token) {
+    ConstDotShorthandEnd data =
+        new ConstDotShorthandEnd(ParserAstType.END, token: token);
+    seen(data);
+  }
 }
 
 class ArgumentsBegin extends ParserAstNode {
@@ -10156,6 +10170,36 @@
   R accept<R>(ParserAstVisitor<R> v) => v.visitDotShorthandHeadHandle(this);
 }
 
+class ConstDotShorthandBegin extends ParserAstNode {
+  final Token token;
+
+  ConstDotShorthandBegin(ParserAstType type, {required this.token})
+      : super("ConstDotShorthand", type);
+
+  @override
+  Map<String, Object?> get deprecatedArguments => {
+        "token": token,
+      };
+
+  @override
+  R accept<R>(ParserAstVisitor<R> v) => v.visitConstDotShorthandBegin(this);
+}
+
+class ConstDotShorthandEnd extends ParserAstNode {
+  final Token token;
+
+  ConstDotShorthandEnd(ParserAstType type, {required this.token})
+      : super("ConstDotShorthand", type);
+
+  @override
+  Map<String, Object?> get deprecatedArguments => {
+        "token": token,
+      };
+
+  @override
+  R accept<R>(ParserAstVisitor<R> v) => v.visitConstDotShorthandEnd(this);
+}
+
 abstract class ParserAstVisitor<R> {
   R visitArgumentsBegin(ArgumentsBegin node);
   R visitArgumentsEnd(ArgumentsEnd node);
@@ -10552,6 +10596,8 @@
   R visitPatternAssignmentHandle(PatternAssignmentHandle node);
   R visitDotShorthandContextHandle(DotShorthandContextHandle node);
   R visitDotShorthandHeadHandle(DotShorthandHeadHandle node);
+  R visitConstDotShorthandBegin(ConstDotShorthandBegin node);
+  R visitConstDotShorthandEnd(ConstDotShorthandEnd node);
 }
 
 class RecursiveParserAstVisitor implements ParserAstVisitor<void> {
@@ -11970,6 +12016,14 @@
   @override
   void visitDotShorthandHeadHandle(DotShorthandHeadHandle node) =>
       node.visitChildren(this);
+
+  @override
+  void visitConstDotShorthandBegin(ConstDotShorthandBegin node) =>
+      node.visitChildren(this);
+
+  @override
+  void visitConstDotShorthandEnd(ConstDotShorthandEnd node) =>
+      node.visitChildren(this);
 }
 
 class RecursiveParserAstVisitorWithDefaultNodeAsync
@@ -13466,4 +13520,12 @@
   @override
   Future<void> visitDotShorthandHeadHandle(DotShorthandHeadHandle node) =>
       defaultNode(node);
+
+  @override
+  Future<void> visitConstDotShorthandBegin(ConstDotShorthandBegin node) =>
+      defaultNode(node);
+
+  @override
+  Future<void> visitConstDotShorthandEnd(ConstDotShorthandEnd node) =>
+      defaultNode(node);
 }
diff --git a/pkg/front_end/test/coverage_suite_expected.dart b/pkg/front_end/test/coverage_suite_expected.dart
index d5bb70f..fffd8d4 100644
--- a/pkg/front_end/test/coverage_suite_expected.dart
+++ b/pkg/front_end/test/coverage_suite_expected.dart
@@ -625,7 +625,7 @@
   ),
   // 100.0%.
   "package:front_end/src/kernel/body_builder.dart": (
-    hitCount: 7217,
+    hitCount: 7246,
     missCount: 0,
   ),
   // 100.0%.
@@ -685,7 +685,7 @@
   ),
   // 100.0%.
   "package:front_end/src/kernel/expression_generator.dart": (
-    hitCount: 2490,
+    hitCount: 2488,
     missCount: 0,
   ),
   // 100.0%.
@@ -1011,7 +1011,7 @@
   ),
   // 100.0%.
   "package:front_end/src/type_inference/inference_visitor.dart": (
-    hitCount: 8221,
+    hitCount: 8236,
     missCount: 0,
   ),
   // 100.0%.
diff --git a/pkg/front_end/test/parser_test_listener.dart b/pkg/front_end/test/parser_test_listener.dart
index 8c61c6c..bc8e248 100644
--- a/pkg/front_end/test/parser_test_listener.dart
+++ b/pkg/front_end/test/parser_test_listener.dart
@@ -3343,4 +3343,18 @@
     seen(token);
     doPrint('handleDotShorthandHead(' '$token)');
   }
+
+  @override
+  void beginConstDotShorthand(Token token) {
+    seen(token);
+    doPrint('beginConstDotShorthand(' '$token)');
+    indent++;
+  }
+
+  @override
+  void endConstDotShorthand(Token token) {
+    indent--;
+    seen(token);
+    doPrint('endConstDotShorthand(' '$token)');
+  }
 }
diff --git a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.expect b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.expect
index 2d33c6f..55174c2 100644
--- a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.expect
+++ b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.expect
@@ -2,8 +2,8 @@
 //
 // Problems in library:
 //
-// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: Expected an identifier, but got '.'.
-// Try inserting an identifier before '.'.
+// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: This requires the experimental 'dot-shorthands' language feature to be enabled.
+// Try passing the '--enable-experiment=dot-shorthands' command line option.
 // test() => A.const.toString();
 //                  ^
 //
diff --git a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.modular.expect b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.modular.expect
index 2d33c6f..55174c2 100644
--- a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.modular.expect
+++ b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.modular.expect
@@ -2,8 +2,8 @@
 //
 // Problems in library:
 //
-// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: Expected an identifier, but got '.'.
-// Try inserting an identifier before '.'.
+// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: This requires the experimental 'dot-shorthands' language feature to be enabled.
+// Try passing the '--enable-experiment=dot-shorthands' command line option.
 // test() => A.const.toString();
 //                  ^
 //
diff --git a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.outline.expect b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.outline.expect
index 99c2cd1..ccba866 100644
--- a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.outline.expect
+++ b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.outline.expect
@@ -1,12 +1,4 @@
 library;
-//
-// Problems in library:
-//
-// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: Expected an identifier, but got '.'.
-// Try inserting an identifier before '.'.
-// test() => A.const.toString();
-//                  ^
-//
 import self as self;
 import "dart:core" as core;
 
diff --git a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.transformed.expect b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.transformed.expect
index 2d33c6f..55174c2 100644
--- a/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/constructor_tearoffs/issue46133.dart.strong.transformed.expect
@@ -2,8 +2,8 @@
 //
 // Problems in library:
 //
-// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: Expected an identifier, but got '.'.
-// Try inserting an identifier before '.'.
+// pkg/front_end/testcases/constructor_tearoffs/issue46133.dart:7:18: Error: This requires the experimental 'dot-shorthands' language feature to be enabled.
+// Try passing the '--enable-experiment=dot-shorthands' command line option.
 // test() => A.const.toString();
 //                  ^
 //
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart
new file mode 100644
index 0000000..f9420c2
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+class Color {
+  final int x;
+  const Color.red(this.x);
+}
+
+void main() {
+  Color c = const .red(1);
+  bool b = .fromEnvironment('env', defaultValue: true);
+}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.expect
new file mode 100644
index 0000000..b5714c2
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void {
+  self::Color c = #C2;
+  core::bool b = core::bool::fromEnvironment("env", defaultValue: true);
+}
+
+constants  {
+  #C1 = 1
+  #C2 = self::Color {x:#C1}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///const_constructor.dart:
+- Color.red (from org-dartlang-testcase:///const_constructor.dart:7:9)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.modular.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.modular.expect
new file mode 100644
index 0000000..b5714c2
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.modular.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void {
+  self::Color c = #C2;
+  core::bool b = core::bool::fromEnvironment("env", defaultValue: true);
+}
+
+constants  {
+  #C1 = 1
+  #C2 = self::Color {x:#C1}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///const_constructor.dart:
+- Color.red (from org-dartlang-testcase:///const_constructor.dart:7:9)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.outline.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.outline.expect
new file mode 100644
index 0000000..08bdb3e
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.outline.expect
@@ -0,0 +1,12 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.transformed.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.transformed.expect
new file mode 100644
index 0000000..a9a6420
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.strong.transformed.expect
@@ -0,0 +1,29 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void {
+  self::Color c = #C2;
+  core::bool b = core::bool::fromEnvironment("env", defaultValue: true);
+}
+
+constants  {
+  #C1 = 1
+  #C2 = self::Color {x:#C1}
+}
+
+Extra constant evaluation status:
+Evaluated: StaticInvocation @ org-dartlang-testcase:///const_constructor.dart:12:12 -> BoolConstant(true)
+Extra constant evaluation: evaluated: 2, effectively constant: 1
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///const_constructor.dart:
+- Color.red (from org-dartlang-testcase:///const_constructor.dart:7:9)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.textual_outline.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.textual_outline.expect
new file mode 100644
index 0000000..509536b
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.textual_outline.expect
@@ -0,0 +1,6 @@
+class Color {
+  final int x;
+  const Color.red(this.x);
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..6c3115f
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor.dart.textual_outline_modelled.expect
@@ -0,0 +1,6 @@
+class Color {
+  const Color.red(this.x);
+  final int x;
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart
new file mode 100644
index 0000000..7ffee03
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+class Color {
+  final int x;
+  const Color.red(this.x);
+  const factory Color.red2(int x) = Color.red;
+}
+
+void main() {
+  Color c = const .red2(1);
+}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.expect
new file mode 100644
index 0000000..b0e35fe
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.expect
@@ -0,0 +1,26 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+  static factory red2(core::int x) → self::Color /* redirection-target: self::Color::red */
+    return new self::Color::red(x);
+}
+static method main() → void {
+  self::Color c = #C2;
+}
+
+constants  {
+  #C1 = 1
+  #C2 = self::Color {x:#C1}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///const_constructor_factory.dart:
+- Color.red (from org-dartlang-testcase:///const_constructor_factory.dart:7:9)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.modular.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.modular.expect
new file mode 100644
index 0000000..b0e35fe
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.modular.expect
@@ -0,0 +1,26 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+  static factory red2(core::int x) → self::Color /* redirection-target: self::Color::red */
+    return new self::Color::red(x);
+}
+static method main() → void {
+  self::Color c = #C2;
+}
+
+constants  {
+  #C1 = 1
+  #C2 = self::Color {x:#C1}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///const_constructor_factory.dart:
+- Color.red (from org-dartlang-testcase:///const_constructor_factory.dart:7:9)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.outline.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.outline.expect
new file mode 100644
index 0000000..67c221b
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.outline.expect
@@ -0,0 +1,14 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+  static factory red2(core::int x) → self::Color /* redirection-target: self::Color::red */
+    return new self::Color::red(x);
+}
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.transformed.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.transformed.expect
new file mode 100644
index 0000000..b0e35fe
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.strong.transformed.expect
@@ -0,0 +1,26 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object /*hasConstConstructor*/  {
+  final field core::int x;
+  const constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+  static factory red2(core::int x) → self::Color /* redirection-target: self::Color::red */
+    return new self::Color::red(x);
+}
+static method main() → void {
+  self::Color c = #C2;
+}
+
+constants  {
+  #C1 = 1
+  #C2 = self::Color {x:#C1}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///const_constructor_factory.dart:
+- Color.red (from org-dartlang-testcase:///const_constructor_factory.dart:7:9)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart)
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.textual_outline.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.textual_outline.expect
new file mode 100644
index 0000000..4236e77
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+class Color {
+  final int x;
+  const Color.red(this.x);
+  const factory Color.red2(int x) = Color.red;
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..64a44b9
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_factory.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+class Color {
+  const Color.red(this.x);
+  const factory Color.red2(int x) = Color.red;
+  final int x;
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart
new file mode 100644
index 0000000..0ae79d3
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+class Color {
+  final int x;
+  Color.red(this.x);
+}
+
+void main() {
+  Color c = const .red(1);
+
+  // With whitespace
+  Color c = const .   red  (1);
+}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.expect
new file mode 100644
index 0000000..ed86fc7
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.expect
@@ -0,0 +1,42 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:9: Error: 'c' is already declared in this scope.
+//   Color c = const .   red  (1);
+//         ^
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:9: Context: Previous declaration of 'c'.
+//   Color c = const .red(1);
+//         ^
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:20: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+// Try using a constructor or factory that is 'const'.
+//   Color c = const .red(1);
+//                    ^^^
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:23: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+// Try using a constructor or factory that is 'const'.
+//   Color c = const .   red  (1);
+//                       ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object {
+  final field core::int x;
+  constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void {
+  self::Color c = invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:20: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+Try using a constructor or factory that is 'const'.
+  Color c = const .red(1);
+                   ^^^" as{TypeError,ForDynamic} self::Color;
+  self::Color c = invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:9: Error: 'c' is already declared in this scope.
+  Color c = const .   red  (1);
+        ^" in invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:23: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+Try using a constructor or factory that is 'const'.
+  Color c = const .   red  (1);
+                      ^^^";
+}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.modular.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.modular.expect
new file mode 100644
index 0000000..ed86fc7
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.modular.expect
@@ -0,0 +1,42 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:9: Error: 'c' is already declared in this scope.
+//   Color c = const .   red  (1);
+//         ^
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:9: Context: Previous declaration of 'c'.
+//   Color c = const .red(1);
+//         ^
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:20: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+// Try using a constructor or factory that is 'const'.
+//   Color c = const .red(1);
+//                    ^^^
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:23: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+// Try using a constructor or factory that is 'const'.
+//   Color c = const .   red  (1);
+//                       ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object {
+  final field core::int x;
+  constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void {
+  self::Color c = invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:20: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+Try using a constructor or factory that is 'const'.
+  Color c = const .red(1);
+                   ^^^" as{TypeError,ForDynamic} self::Color;
+  self::Color c = invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:9: Error: 'c' is already declared in this scope.
+  Color c = const .   red  (1);
+        ^" in invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:23: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+Try using a constructor or factory that is 'const'.
+  Color c = const .   red  (1);
+                      ^^^";
+}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.outline.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.outline.expect
new file mode 100644
index 0000000..5a5d6bd
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.outline.expect
@@ -0,0 +1,11 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object {
+  final field core::int x;
+  constructor red(core::int x) → self::Color
+    ;
+}
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.transformed.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.transformed.expect
new file mode 100644
index 0000000..8f3653a
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.strong.transformed.expect
@@ -0,0 +1,42 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:9: Error: 'c' is already declared in this scope.
+//   Color c = const .   red  (1);
+//         ^
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:9: Context: Previous declaration of 'c'.
+//   Color c = const .red(1);
+//         ^
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:20: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+// Try using a constructor or factory that is 'const'.
+//   Color c = const .red(1);
+//                    ^^^
+//
+// pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:23: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+// Try using a constructor or factory that is 'const'.
+//   Color c = const .   red  (1);
+//                       ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class Color extends core::Object {
+  final field core::int x;
+  constructor red(core::int x) → self::Color
+    : self::Color::x = x, super core::Object::•()
+    ;
+}
+static method main() → void {
+  self::Color c = invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:11:20: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+Try using a constructor or factory that is 'const'.
+  Color c = const .red(1);
+                   ^^^" as{TypeError,ForDynamic,Unchecked} self::Color;
+  self::Color c = invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:9: Error: 'c' is already declared in this scope.
+  Color c = const .   red  (1);
+        ^" in invalid-expression "pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart:14:23: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
+Try using a constructor or factory that is 'const'.
+  Color c = const .   red  (1);
+                      ^^^";
+}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.textual_outline.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.textual_outline.expect
new file mode 100644
index 0000000..42b527b
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.textual_outline.expect
@@ -0,0 +1,6 @@
+class Color {
+  final int x;
+  Color.red(this.x);
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..8c85a44
--- /dev/null
+++ b/pkg/front_end/testcases/dot_shorthands/const_constructor_nonconst.dart.textual_outline_modelled.expect
@@ -0,0 +1,6 @@
+class Color {
+  Color.red(this.x);
+  final int x;
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index 71246f1..30d8b1e 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -172,6 +172,7 @@
 dart2js/tear_off_patch/main: semiFuzzFailureOnForceRebuildBodies # needs custom libraries.json (and platform?) not setup here
 
 constructor_tearoffs/call_instantiation: TypeCheckError
+dot_shorthands/const_constructor_nonconst: RuntimeError
 enhanced_enums/declared_hashcode: TypeCheckError
 enhanced_enums/declared_index: TypeCheckError
 enhanced_enums/simple_mixins: RuntimeError