[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