Skip to content

Commit 4967abb

Browse files
kallentuCommit Queue
authored and
Commit Queue
committed
[cfe] Handle dot shorthand invocations.
This CL adds the resolution of dot shorthand invocations. At the point of parsing, we can't be sure whether the invocation is a method invocation or a constructor invocation. We'll resolve the name with the given context type to find out. Bug: #59758 Change-Id: I136ab6c7522fe24d98a4613d102089d50c4cd8cd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412800 Reviewed-by: Chloe Stefantsova <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
1 parent 7139b68 commit 4967abb

27 files changed

+429
-11
lines changed

pkg/front_end/lib/src/kernel/body_builder.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10016,10 +10016,15 @@ class BodyBuilder extends StackListenerImpl
1001610016
assert(checkState(token, [ValueKinds.Selector]));
1001710017
Selector selector = pop() as Selector;
1001810018
if (libraryFeatures.dotShorthands.isEnabled) {
10019-
// TODO(kallentu): Handle invocations.
10020-
10021-
push(forest.createDotShorthandPropertyGet(
10022-
offsetForToken(token), selector.name));
10019+
if (selector is InvocationSelector) {
10020+
// e.g. `.parse(2)`
10021+
push(forest.createDotShorthandInvocation(
10022+
offsetForToken(token), selector.name, selector.arguments));
10023+
} else if (selector is PropertySelector) {
10024+
// e.g. `.zero`
10025+
push(forest.createDotShorthandPropertyGet(
10026+
offsetForToken(token), selector.name));
10027+
}
1002310028
}
1002410029
}
1002510030
}

pkg/front_end/lib/src/kernel/forest.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,11 @@ class Forest {
933933
return new DotShorthand(innerExpression)..fileOffset = fileOffset;
934934
}
935935

936+
DotShorthandInvocation createDotShorthandInvocation(
937+
int fileOffset, Name name, Arguments arguments) {
938+
return new DotShorthandInvocation(name, arguments)..fileOffset = fileOffset;
939+
}
940+
936941
DotShorthandPropertyGet createDotShorthandPropertyGet(
937942
int fileOffset, Name name) {
938943
return new DotShorthandPropertyGet(name)..fileOffset = fileOffset;

pkg/front_end/lib/src/kernel/internal_ast.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3248,6 +3248,38 @@ class DotShorthand extends InternalExpression {
32483248
}
32493249
}
32503250

3251+
/// Internal expression for a dot shorthand head with arguments.
3252+
/// (e.g. `.parse(42)`).
3253+
///
3254+
/// This node could represent a shorthand of a static method or a named
3255+
/// constructor.
3256+
class DotShorthandInvocation extends InternalExpression {
3257+
Name name;
3258+
3259+
Arguments arguments;
3260+
3261+
DotShorthandInvocation(this.name, this.arguments);
3262+
3263+
@override
3264+
ExpressionInferenceResult acceptInference(
3265+
InferenceVisitorImpl visitor, DartType typeContext) {
3266+
return visitor.visitDotShorthandInvocation(this, typeContext);
3267+
}
3268+
3269+
@override
3270+
String toString() {
3271+
return "DotShorthandInvocation(${toStringInternal()})";
3272+
}
3273+
3274+
@override
3275+
// Coverage-ignore(suite): Not run.
3276+
void toTextInternal(AstPrinter printer) {
3277+
printer.write('.');
3278+
printer.writeName(name);
3279+
printer.writeArguments(arguments);
3280+
}
3281+
}
3282+
32513283
/// Internal expression for a dot shorthand head with no arguments.
32523284
/// (e.g. `.zero`).
32533285
///

pkg/front_end/lib/src/type_inference/inference_visitor.dart

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import '../base/instrumentation.dart'
3030
InstrumentationValueForType,
3131
InstrumentationValueForTypeArgs;
3232
import '../base/problems.dart' as problems
33-
show internalProblem, unhandled, unsupported;
33+
show internalProblem, unhandled, unsupported, unimplemented;
3434
import '../base/uri_offset.dart';
3535
import '../codes/cfe_codes.dart';
3636
import '../kernel/body_builder.dart' show combineStatements;
@@ -12111,6 +12111,63 @@ class InferenceVisitorImpl extends InferenceVisitorBase
1211112111
return new ExpressionInferenceResult(rewrittenType, rewrittenExpr);
1211212112
}
1211312113

12114+
ExpressionInferenceResult visitDotShorthandInvocation(
12115+
DotShorthandInvocation node, DartType typeContext) {
12116+
// Use the previously cached context type to determine the declaration
12117+
// member that we're trying to find.
12118+
DartType cachedContext = getDotShorthandContext().unwrapTypeSchemaView();
12119+
Member? member = findInterfaceMember(
12120+
cachedContext, node.name, node.fileOffset,
12121+
includeExtensionMethods: false,
12122+
isSetter: false,
12123+
isDotShorthand: true)
12124+
.member;
12125+
12126+
Expression expr;
12127+
if (member is Procedure) {
12128+
expr = new StaticInvocation(member, node.arguments)
12129+
..fileOffset = node.fileOffset;
12130+
} else if (member == null && cachedContext is TypeDeclarationType) {
12131+
// Couldn't find a static method in the declaration so we'll try and find
12132+
// a constructor of that name instead.
12133+
Member? constructor =
12134+
findConstructor(cachedContext, node.name, node.fileOffset);
12135+
if (constructor is Constructor) {
12136+
// TODO(kallentu): Const constructors.
12137+
expr = new ConstructorInvocation(constructor, node.arguments,
12138+
isConst: false)
12139+
..fileOffset = node.fileOffset;
12140+
} else if (constructor is Procedure) {
12141+
// [constructor] can be a [Procedure] if we have an extension type
12142+
// constructor.
12143+
expr = new StaticInvocation(constructor, node.arguments)
12144+
..fileOffset = node.fileOffset;
12145+
} else {
12146+
// Coverage-ignore-block(suite): Not run.
12147+
// TODO(kallentu): This is temporary. Build a problem with an error
12148+
// specific to not being able to find a member named [node.name].
12149+
problems.unimplemented(
12150+
'Cannot find dot shorthand member of name ${node.name}',
12151+
node.fileOffset,
12152+
helper.uri);
12153+
}
12154+
} else {
12155+
// Coverage-ignore-block(suite): Not run.
12156+
// TODO(kallentu): This is temporary. Build a problem with an error on the
12157+
// bad context type.
12158+
problems.unimplemented(
12159+
'Cannot find dot shorthand member of name ${node.name} with '
12160+
'context $cachedContext',
12161+
node.fileOffset,
12162+
helper.uri);
12163+
}
12164+
12165+
ExpressionInferenceResult expressionInferenceResult =
12166+
inferExpression(expr, cachedContext);
12167+
flowAnalysis.forwardExpression(expressionInferenceResult.expression, node);
12168+
return expressionInferenceResult;
12169+
}
12170+
1211412171
ExpressionInferenceResult visitDotShorthandPropertyGet(
1211512172
DotShorthandPropertyGet node, DartType typeContext) {
1211612173
// Use the previously cached context type to determine the declaration
@@ -12123,9 +12180,13 @@ class InferenceVisitorImpl extends InferenceVisitorBase
1212312180

1212412181
ExpressionInferenceResult expressionInferenceResult;
1212512182
if (member == null) {
12183+
// Coverage-ignore-block(suite): Not run.
1212612184
// TODO(kallentu): This is temporary. Build a problem with an error
1212712185
// specific to not being able to find a member named [node.name].
12128-
throw 'Error: Cannot find dot shorthand member.';
12186+
problems.unimplemented(
12187+
'Cannot find dot shorthand member of name ${node.name}',
12188+
node.fileOffset,
12189+
helper.uri);
1212912190
} else if (member is Procedure && !member.isGetter) {
1213012191
// Tearoff like `Object.new`;
1213112192
expressionInferenceResult =

pkg/front_end/lib/src/type_inference/inference_visitor_base.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,28 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
11691169
return defaultTarget;
11701170
}
11711171

1172+
/// Finds a constructor of [type] called [name].
1173+
Member? findConstructor(TypeDeclarationType type, Name name, int fileOffset) {
1174+
assert(isKnown(type));
1175+
1176+
// TODO(Dart Model team): Seems like an abstraction level issue to require
1177+
// going from `Class` objects back to builders to find a `Member`.
1178+
DeclarationBuilder builder;
1179+
switch (type) {
1180+
case InterfaceType():
1181+
builder = engine.hierarchyBuilder.loader
1182+
.computeClassBuilderFromTargetClass(type.classNode);
1183+
case ExtensionType():
1184+
builder = engine.hierarchyBuilder.loader
1185+
.computeExtensionTypeBuilderFromTargetExtensionType(
1186+
type.extensionTypeDeclaration);
1187+
}
1188+
1189+
MemberBuilder? constructorBuilder = builder.findConstructorOrFactory(
1190+
name.text, fileOffset, helper.uri, libraryBuilder);
1191+
return constructorBuilder?.invokeTarget;
1192+
}
1193+
11721194
/// Finds a member of [receiverType] called [name], and if it is found,
11731195
/// reports it through instrumentation using [fileOffset].
11741196
///

pkg/front_end/test/coverage_suite_expected.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
590590
),
591591
// 100.0%.
592592
"package:front_end/src/kernel/body_builder.dart": (
593-
hitCount: 7205,
593+
hitCount: 7213,
594594
missCount: 0,
595595
),
596596
// 100.0%.
@@ -660,7 +660,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
660660
),
661661
// 100.0%.
662662
"package:front_end/src/kernel/forest.dart": (
663-
hitCount: 402,
663+
hitCount: 405,
664664
missCount: 0,
665665
),
666666
// 100.0%.
@@ -720,7 +720,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
720720
),
721721
// 100.0%.
722722
"package:front_end/src/kernel/internal_ast.dart": (
723-
hitCount: 551,
723+
hitCount: 554,
724724
missCount: 0,
725725
),
726726
// 100.0%.
@@ -976,12 +976,12 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
976976
),
977977
// 100.0%.
978978
"package:front_end/src/type_inference/inference_visitor.dart": (
979-
hitCount: 8178,
979+
hitCount: 8208,
980980
missCount: 0,
981981
),
982982
// 100.0%.
983983
"package:front_end/src/type_inference/inference_visitor_base.dart": (
984-
hitCount: 2451,
984+
hitCount: 2477,
985985
missCount: 0,
986986
),
987987
// 100.0%.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
class Color {
6+
final int x;
7+
Color.red() : x = 1;
8+
Color(this.x);
9+
}
10+
11+
void main() {
12+
Color c = .red();
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class Color extends core::Object {
6+
final field core::int x;
7+
constructor red() → self::Color
8+
: self::Color::x = 1, super core::Object::•()
9+
;
10+
constructor •(core::int x) → self::Color
11+
: self::Color::x = x, super core::Object::•()
12+
;
13+
}
14+
static method main() → void {
15+
self::Color c = new self::Color::red();
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class Color extends core::Object {
6+
final field core::int x;
7+
constructor red() → self::Color
8+
: self::Color::x = 1, super core::Object::•()
9+
;
10+
constructor •(core::int x) → self::Color
11+
: self::Color::x = x, super core::Object::•()
12+
;
13+
}
14+
static method main() → void {
15+
self::Color c = new self::Color::red();
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class Color extends core::Object {
6+
final field core::int x;
7+
constructor red() → self::Color
8+
;
9+
constructor •(core::int x) → self::Color
10+
;
11+
}
12+
static method main() → void
13+
;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class Color extends core::Object {
6+
final field core::int x;
7+
constructor red() → self::Color
8+
: self::Color::x = 1, super core::Object::•()
9+
;
10+
constructor •(core::int x) → self::Color
11+
: self::Color::x = x, super core::Object::•()
12+
;
13+
}
14+
static method main() → void {
15+
self::Color c = new self::Color::red();
16+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Color {
2+
final int x;
3+
Color.red() : x = 1;
4+
Color(this.x);
5+
}
6+
7+
void main() {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Color {
2+
Color(this.x);
3+
Color.red() : x = 1;
4+
final int x;
5+
}
6+
7+
void main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
extension type IntegerExt(int integer) {
6+
IntegerExt.regular(this.integer);
7+
}
8+
9+
void main() {
10+
IntegerExt c = .regular(1);
11+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
extension type IntegerExt(core::int integer) {
6+
abstract extension-type-member representation-field get integer() → core::int;
7+
constructor • = self::IntegerExt|constructor#;
8+
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
9+
constructor regular = self::IntegerExt|constructor#regular;
10+
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
11+
}
12+
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
13+
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
14+
return #this;
15+
}
16+
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
17+
return self::IntegerExt|constructor#(integer);
18+
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
19+
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
20+
return #this;
21+
}
22+
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
23+
return self::IntegerExt|constructor#regular(integer);
24+
static method main() → void {
25+
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
extension type IntegerExt(core::int integer) {
6+
abstract extension-type-member representation-field get integer() → core::int;
7+
constructor • = self::IntegerExt|constructor#;
8+
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
9+
constructor regular = self::IntegerExt|constructor#regular;
10+
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
11+
}
12+
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
13+
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
14+
return #this;
15+
}
16+
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
17+
return self::IntegerExt|constructor#(integer);
18+
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
19+
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
20+
return #this;
21+
}
22+
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
23+
return self::IntegerExt|constructor#regular(integer);
24+
static method main() → void {
25+
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
26+
}

0 commit comments

Comments
 (0)