[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: https://github.com/dart-lang/sdk/issues/59758
Change-Id: I136ab6c7522fe24d98a4613d102089d50c4cd8cd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412800
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
This commit is contained in:
Kallen Tu
2025-03-04 15:32:59 -08:00
committed by Commit Queue
parent 7139b68c69
commit 4967abbb3a
27 changed files with 429 additions and 11 deletions
@@ -10016,10 +10016,15 @@ class BodyBuilder extends StackListenerImpl
assert(checkState(token, [ValueKinds.Selector]));
Selector selector = pop() as Selector;
if (libraryFeatures.dotShorthands.isEnabled) {
// TODO(kallentu): Handle invocations.
push(forest.createDotShorthandPropertyGet(
offsetForToken(token), selector.name));
if (selector is InvocationSelector) {
// e.g. `.parse(2)`
push(forest.createDotShorthandInvocation(
offsetForToken(token), selector.name, selector.arguments));
} else if (selector is PropertySelector) {
// e.g. `.zero`
push(forest.createDotShorthandPropertyGet(
offsetForToken(token), selector.name));
}
}
}
}
+5
View File
@@ -933,6 +933,11 @@ class Forest {
return new DotShorthand(innerExpression)..fileOffset = fileOffset;
}
DotShorthandInvocation createDotShorthandInvocation(
int fileOffset, Name name, Arguments arguments) {
return new DotShorthandInvocation(name, arguments)..fileOffset = fileOffset;
}
DotShorthandPropertyGet createDotShorthandPropertyGet(
int fileOffset, Name name) {
return new DotShorthandPropertyGet(name)..fileOffset = fileOffset;
@@ -3248,6 +3248,38 @@ class DotShorthand extends InternalExpression {
}
}
/// Internal expression for a dot shorthand head with arguments.
/// (e.g. `.parse(42)`).
///
/// This node could represent a shorthand of a static method or a named
/// constructor.
class DotShorthandInvocation extends InternalExpression {
Name name;
Arguments arguments;
DotShorthandInvocation(this.name, this.arguments);
@override
ExpressionInferenceResult acceptInference(
InferenceVisitorImpl visitor, DartType typeContext) {
return visitor.visitDotShorthandInvocation(this, typeContext);
}
@override
String toString() {
return "DotShorthandInvocation(${toStringInternal()})";
}
@override
// Coverage-ignore(suite): Not run.
void toTextInternal(AstPrinter printer) {
printer.write('.');
printer.writeName(name);
printer.writeArguments(arguments);
}
}
/// Internal expression for a dot shorthand head with no arguments.
/// (e.g. `.zero`).
///
@@ -30,7 +30,7 @@ import '../base/instrumentation.dart'
InstrumentationValueForType,
InstrumentationValueForTypeArgs;
import '../base/problems.dart' as problems
show internalProblem, unhandled, unsupported;
show internalProblem, unhandled, unsupported, unimplemented;
import '../base/uri_offset.dart';
import '../codes/cfe_codes.dart';
import '../kernel/body_builder.dart' show combineStatements;
@@ -12111,6 +12111,63 @@ class InferenceVisitorImpl extends InferenceVisitorBase
return new ExpressionInferenceResult(rewrittenType, rewrittenExpr);
}
ExpressionInferenceResult visitDotShorthandInvocation(
DotShorthandInvocation node, DartType typeContext) {
// Use the previously cached context type to determine the declaration
// member that we're trying to find.
DartType cachedContext = getDotShorthandContext().unwrapTypeSchemaView();
Member? member = findInterfaceMember(
cachedContext, node.name, node.fileOffset,
includeExtensionMethods: false,
isSetter: false,
isDotShorthand: true)
.member;
Expression expr;
if (member is Procedure) {
expr = new StaticInvocation(member, node.arguments)
..fileOffset = node.fileOffset;
} else if (member == null && cachedContext is TypeDeclarationType) {
// Couldn't find a static method in the declaration so we'll try and find
// a constructor of that name instead.
Member? constructor =
findConstructor(cachedContext, node.name, node.fileOffset);
if (constructor is Constructor) {
// TODO(kallentu): Const constructors.
expr = new ConstructorInvocation(constructor, node.arguments,
isConst: false)
..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;
} else {
// Coverage-ignore-block(suite): Not run.
// TODO(kallentu): This is temporary. Build a problem with an error
// specific to not being able to find a member named [node.name].
problems.unimplemented(
'Cannot find dot shorthand member of name ${node.name}',
node.fileOffset,
helper.uri);
}
} else {
// Coverage-ignore-block(suite): Not run.
// TODO(kallentu): This is temporary. Build a problem with an error on the
// bad context type.
problems.unimplemented(
'Cannot find dot shorthand member of name ${node.name} with '
'context $cachedContext',
node.fileOffset,
helper.uri);
}
ExpressionInferenceResult expressionInferenceResult =
inferExpression(expr, cachedContext);
flowAnalysis.forwardExpression(expressionInferenceResult.expression, node);
return expressionInferenceResult;
}
ExpressionInferenceResult visitDotShorthandPropertyGet(
DotShorthandPropertyGet node, DartType typeContext) {
// Use the previously cached context type to determine the declaration
@@ -12123,9 +12180,13 @@ class InferenceVisitorImpl extends InferenceVisitorBase
ExpressionInferenceResult expressionInferenceResult;
if (member == null) {
// Coverage-ignore-block(suite): Not run.
// TODO(kallentu): This is temporary. Build a problem with an error
// specific to not being able to find a member named [node.name].
throw 'Error: Cannot find dot shorthand member.';
problems.unimplemented(
'Cannot find dot shorthand member of name ${node.name}',
node.fileOffset,
helper.uri);
} else if (member is Procedure && !member.isGetter) {
// Tearoff like `Object.new`;
expressionInferenceResult =
@@ -1169,6 +1169,28 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
return defaultTarget;
}
/// Finds a constructor of [type] called [name].
Member? findConstructor(TypeDeclarationType type, Name name, int fileOffset) {
assert(isKnown(type));
// TODO(Dart Model team): Seems like an abstraction level issue to require
// going from `Class` objects back to builders to find a `Member`.
DeclarationBuilder builder;
switch (type) {
case InterfaceType():
builder = engine.hierarchyBuilder.loader
.computeClassBuilderFromTargetClass(type.classNode);
case ExtensionType():
builder = engine.hierarchyBuilder.loader
.computeExtensionTypeBuilderFromTargetExtensionType(
type.extensionTypeDeclaration);
}
MemberBuilder? constructorBuilder = builder.findConstructorOrFactory(
name.text, fileOffset, helper.uri, libraryBuilder);
return constructorBuilder?.invokeTarget;
}
/// Finds a member of [receiverType] called [name], and if it is found,
/// reports it through instrumentation using [fileOffset].
///
@@ -590,7 +590,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/kernel/body_builder.dart": (
hitCount: 7205,
hitCount: 7213,
missCount: 0,
),
// 100.0%.
@@ -660,7 +660,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/kernel/forest.dart": (
hitCount: 402,
hitCount: 405,
missCount: 0,
),
// 100.0%.
@@ -720,7 +720,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/kernel/internal_ast.dart": (
hitCount: 551,
hitCount: 554,
missCount: 0,
),
// 100.0%.
@@ -976,12 +976,12 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
),
// 100.0%.
"package:front_end/src/type_inference/inference_visitor.dart": (
hitCount: 8178,
hitCount: 8208,
missCount: 0,
),
// 100.0%.
"package:front_end/src/type_inference/inference_visitor_base.dart": (
hitCount: 2451,
hitCount: 2477,
missCount: 0,
),
// 100.0%.
@@ -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;
Color.red() : x = 1;
Color(this.x);
}
void main() {
Color c = .red();
}
@@ -0,0 +1,16 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor red() → self::Color
: self::Color::x = 1, super core::Object::•()
;
constructor •(core::int x) → self::Color
: self::Color::x = x, super core::Object::•()
;
}
static method main() → void {
self::Color c = new self::Color::red();
}
@@ -0,0 +1,16 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor red() → self::Color
: self::Color::x = 1, super core::Object::•()
;
constructor •(core::int x) → self::Color
: self::Color::x = x, super core::Object::•()
;
}
static method main() → void {
self::Color c = new self::Color::red();
}
@@ -0,0 +1,13 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor red() → self::Color
;
constructor •(core::int x) → self::Color
;
}
static method main() → void
;
@@ -0,0 +1,16 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor red() → self::Color
: self::Color::x = 1, super core::Object::•()
;
constructor •(core::int x) → self::Color
: self::Color::x = x, super core::Object::•()
;
}
static method main() → void {
self::Color c = new self::Color::red();
}
@@ -0,0 +1,7 @@
class Color {
final int x;
Color.red() : x = 1;
Color(this.x);
}
void main() {}
@@ -0,0 +1,7 @@
class Color {
Color(this.x);
Color.red() : x = 1;
final int x;
}
void main() {}
@@ -0,0 +1,11 @@
// 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.
extension type IntegerExt(int integer) {
IntegerExt.regular(this.integer);
}
void main() {
IntegerExt c = .regular(1);
}
@@ -0,0 +1,26 @@
library;
import self as self;
import "dart:core" as core;
extension type IntegerExt(core::int integer) {
abstract extension-type-member representation-field get integer() → core::int;
constructor • = self::IntegerExt|constructor#;
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
constructor regular = self::IntegerExt|constructor#regular;
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
}
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
return #this;
}
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#(integer);
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
return #this;
}
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#regular(integer);
static method main() → void {
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
}
@@ -0,0 +1,26 @@
library;
import self as self;
import "dart:core" as core;
extension type IntegerExt(core::int integer) {
abstract extension-type-member representation-field get integer() → core::int;
constructor • = self::IntegerExt|constructor#;
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
constructor regular = self::IntegerExt|constructor#regular;
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
}
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
return #this;
}
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#(integer);
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
return #this;
}
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#regular(integer);
static method main() → void {
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
}
@@ -0,0 +1,21 @@
library;
import self as self;
import "dart:core" as core;
extension type IntegerExt(core::int integer) {
abstract extension-type-member representation-field get integer() → core::int;
constructor • = self::IntegerExt|constructor#;
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
constructor regular = self::IntegerExt|constructor#regular;
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
}
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
;
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#(integer);
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
;
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#regular(integer);
static method main() → void
;
@@ -0,0 +1,26 @@
library;
import self as self;
import "dart:core" as core;
extension type IntegerExt(core::int integer) {
abstract extension-type-member representation-field get integer() → core::int;
constructor • = self::IntegerExt|constructor#;
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
constructor regular = self::IntegerExt|constructor#regular;
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
}
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
return #this;
}
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#(integer);
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
return #this;
}
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
return self::IntegerExt|constructor#regular(integer);
static method main() → void {
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
}
@@ -0,0 +1,5 @@
extension type IntegerExt(int integer) {
IntegerExt.regular(this.integer);
}
void main() {}
@@ -0,0 +1,5 @@
extension type IntegerExt(int integer) {
IntegerExt.regular(this.integer);
}
void main() {}
@@ -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;
static Color red() => Color(1);
Color(this.x);
}
void main() {
Color c = .red();
}
@@ -0,0 +1,15 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor •(core::int x) → self::Color
: self::Color::x = x, super core::Object::•()
;
static method red() → self::Color
return new self::Color::•(1);
}
static method main() → void {
self::Color c = self::Color::red();
}
@@ -0,0 +1,15 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor •(core::int x) → self::Color
: self::Color::x = x, super core::Object::•()
;
static method red() → self::Color
return new self::Color::•(1);
}
static method main() → void {
self::Color c = self::Color::red();
}
@@ -0,0 +1,13 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor •(core::int x) → self::Color
;
static method red() → self::Color
;
}
static method main() → void
;
@@ -0,0 +1,15 @@
library;
import self as self;
import "dart:core" as core;
class Color extends core::Object {
final field core::int x;
constructor •(core::int x) → self::Color
: self::Color::x = x, super core::Object::•()
;
static method red() → self::Color
return new self::Color::•(1);
}
static method main() → void {
self::Color c = self::Color::red();
}
@@ -0,0 +1,7 @@
class Color {
final int x;
static Color red() => Color(1);
Color(this.x);
}
void main() {}
@@ -0,0 +1,7 @@
class Color {
Color(this.x);
final int x;
static Color red() => Color(1);
}
void main() {}