[analyzer] Dot shorthands: Build ASTs, add isDotShorthand flag, and handle method invocations.
With this CL, we build the `DotShorthandInvocation` AST as soon as we find a `MethodInvocation` when parsing a dot shorthand head. The context is saved as we encounter ASTs with the `isDotShorthand` flag enabled (which currently is just the invocation and property get head, more will be added later). We pop the context after resolving the dot shorthand head and using the context for resolution. The resolution of dot shorthand invocations is handled by the `MethodInvocationInferrer` where most of the logic was added in https://dart-review.googlesource.com/c/sdk/+/421560. In this CL, we're calling the `resolveDotShorthand` entry point for the very basic resolving. This is the groundwork that we'll build off of for constructor invocations, extension type invocations and other cases. Some co19 tests are crashing, as per expected, but I added resolution tests for the .shorthand invocations and ast building tests. Bug: https://github.com/dart-lang/sdk/issues/59835 Change-Id: I62bcb5fecb3c4fdfcf29d6c079d5d77547c4a21a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419780 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Kallen Tu <kallentu@google.com>
This commit is contained in:
@@ -2345,6 +2345,18 @@ mixin TypeAnalyzer<
|
||||
/// Queries whether [pattern] is a variable pattern.
|
||||
bool isVariablePattern(Node pattern);
|
||||
|
||||
/// Pops the top of the [_dotShorthands] stack when we're finished resolving
|
||||
/// the dot shorthand head that requires the recent-most context.
|
||||
void popDotShorthandContext() {
|
||||
_dotShorthands.removeLast();
|
||||
}
|
||||
|
||||
/// Pushes the [context] onto the stack to use when we resolve the dot
|
||||
/// shorthand head.
|
||||
void pushDotShorthandContext(SharedTypeSchemaView context) {
|
||||
_dotShorthands.add(context);
|
||||
}
|
||||
|
||||
/// Returns the type of the property in [receiverType] that corresponds to
|
||||
/// the name of the [field]. If the property cannot be resolved, the client
|
||||
/// should report an error, and return `dynamic` for recovery.
|
||||
|
||||
@@ -5407,6 +5407,7 @@ abstract final class DotShorthandInvocation extends InvocationExpression {
|
||||
}
|
||||
|
||||
final class DotShorthandInvocationImpl extends InvocationExpressionImpl
|
||||
with DotShorthandMixin
|
||||
implements DotShorthandInvocation {
|
||||
@override
|
||||
final Token period;
|
||||
@@ -5466,6 +5467,13 @@ final class DotShorthandInvocationImpl extends InvocationExpressionImpl
|
||||
}
|
||||
}
|
||||
|
||||
base mixin DotShorthandMixin on AstNodeImpl {
|
||||
/// Whether the AST node is a dot shorthand and has a dot shorthand head
|
||||
/// ([DotShorthandInvocation] or [DotShorthandPropertyAccess]) as its
|
||||
/// inner-most target.
|
||||
bool isDotShorthand = false;
|
||||
}
|
||||
|
||||
/// A node that represents a dot shorthand property access of a field or a
|
||||
/// static getter.
|
||||
///
|
||||
@@ -5483,6 +5491,7 @@ abstract final class DotShorthandPropertyAccess extends Expression {
|
||||
}
|
||||
|
||||
final class DotShorthandPropertyAccessImpl extends ExpressionImpl
|
||||
with DotShorthandMixin
|
||||
implements DotShorthandPropertyAccess {
|
||||
@override
|
||||
final Token period;
|
||||
|
||||
@@ -56,7 +56,7 @@ class MethodInvocationResolver with ScopeHelpers {
|
||||
final InvocationInferenceHelper _inferenceHelper;
|
||||
|
||||
/// The invocation being resolved.
|
||||
MethodInvocationImpl? _invocation;
|
||||
InvocationExpressionImpl? _invocation;
|
||||
|
||||
/// The [Name] object of the invocation being resolved by [resolve].
|
||||
Name? _currentName;
|
||||
@@ -207,6 +207,8 @@ class MethodInvocationResolver with ScopeHelpers {
|
||||
FunctionExpressionInvocationImpl? resolveDotShorthand(
|
||||
DotShorthandInvocationImpl node,
|
||||
List<WhyNotPromotedGetter> whyNotPromotedArguments) {
|
||||
_invocation = node;
|
||||
|
||||
var contextType = _resolver.getDotShorthandContext().unwrapTypeSchemaView();
|
||||
// TODO(kallentu): Dot shorthands work - Support other context types
|
||||
if (contextType is InterfaceTypeImpl) {
|
||||
|
||||
@@ -4066,20 +4066,44 @@ class AstBuilder extends StackListener {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(kallentu): Handle dot shorthands.
|
||||
var dotShorthand = pop() as ExpressionImpl;
|
||||
if (dotShorthand is DotShorthandMixin) {
|
||||
(dotShorthand as DotShorthandMixin).isDotShorthand = true;
|
||||
}
|
||||
// TODO(kallentu): Add this assert once we've applied the DotShorthandMixin
|
||||
// on all possible expressions that can be a dot shorthand.
|
||||
// } else {
|
||||
// assert(
|
||||
// false,
|
||||
// "'$dotShorthand' must be a 'DotShorthandMixin' because we "
|
||||
// "should only call 'handleDotShorthandContext' after parsing "
|
||||
// "expressions that have a context type we can cache.");
|
||||
// }
|
||||
push(dotShorthand);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleDotShorthandHead(Token token) {
|
||||
void handleDotShorthandHead(Token periodToken) {
|
||||
debugEvent("DotShorthandHead");
|
||||
if (!enabledDotShorthands) {
|
||||
_reportFeatureNotEnabled(
|
||||
feature: ExperimentalFeatures.dot_shorthands,
|
||||
startToken: token,
|
||||
startToken: periodToken,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(kallentu): Handle dot shorthands.
|
||||
var operand = pop() as ExpressionImpl;
|
||||
// TODO(kallentu): Handle property access case.
|
||||
if (operand is MethodInvocationImpl) {
|
||||
push(DotShorthandInvocationImpl(
|
||||
period: periodToken,
|
||||
memberName: operand.methodName,
|
||||
typeArguments: operand.typeArguments,
|
||||
argumentList: operand.argumentList,
|
||||
));
|
||||
} else {
|
||||
push(operand);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -163,6 +163,19 @@ class ElementResolver {
|
||||
_resolveAnnotations(node.metadata);
|
||||
}
|
||||
|
||||
/// Resolves the dot shorthand invocation, [node].
|
||||
///
|
||||
/// If [node] is rewritten to be a [FunctionExpressionInvocation] in the
|
||||
/// process, then returns that new node. Otherwise, returns `null`.
|
||||
FunctionExpressionInvocationImpl? visitDotShorthandInvocation(
|
||||
covariant DotShorthandInvocationImpl node,
|
||||
{List<WhyNotPromotedGetter>? whyNotPromotedArguments,
|
||||
required TypeImpl contextType}) {
|
||||
whyNotPromotedArguments ??= [];
|
||||
return _methodInvocationResolver.resolveDotShorthand(
|
||||
node, whyNotPromotedArguments);
|
||||
}
|
||||
|
||||
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
|
||||
_resolveAnnotations(node.metadata);
|
||||
}
|
||||
|
||||
@@ -2278,9 +2278,40 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDotShorthandInvocation(DotShorthandInvocation node,
|
||||
void visitDotShorthandInvocation(covariant DotShorthandInvocationImpl node,
|
||||
{TypeImpl contextType = UnknownInferredType.instance}) {
|
||||
throw UnimplementedError('TODO(kallentu)');
|
||||
inferenceLogWriter?.enterExpression(node, contextType);
|
||||
|
||||
// If [isDotShorthand] is set, cache the context type for resolution.
|
||||
if (node.isDotShorthand) {
|
||||
pushDotShorthandContext(SharedTypeSchemaView(contextType));
|
||||
}
|
||||
|
||||
checkUnreachableNode(node);
|
||||
var whyNotPromotedArguments =
|
||||
<Map<SharedTypeView, NonPromotionReason> Function()>[];
|
||||
|
||||
node.typeArguments?.accept(this);
|
||||
var functionRewrite = elementResolver.visitDotShorthandInvocation(node,
|
||||
whyNotPromotedArguments: whyNotPromotedArguments,
|
||||
contextType: contextType);
|
||||
// TODO(kallentu): Handle constructors.
|
||||
if (functionRewrite is FunctionExpressionInvocationImpl) {
|
||||
_resolveRewrittenFunctionExpressionInvocation(
|
||||
functionRewrite, whyNotPromotedArguments,
|
||||
contextType: contextType);
|
||||
}
|
||||
var replacement =
|
||||
insertGenericFunctionInstantiation(node, contextType: contextType);
|
||||
checkForArgumentTypesNotAssignableInList(
|
||||
node.argumentList, whyNotPromotedArguments);
|
||||
_insertImplicitCallReference(replacement, contextType: contextType);
|
||||
|
||||
if (node.isDotShorthand) {
|
||||
popDotShorthandContext();
|
||||
}
|
||||
|
||||
inferenceLogWriter?.exitExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -4279,6 +4310,8 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
|
||||
name = nameNodeName is PrefixedIdentifier
|
||||
? nameNodeName.identifier.name
|
||||
: '${nameNodeName.name}.new';
|
||||
} else if (nameNode is DotShorthandInvocation) {
|
||||
name = nameNode.memberName.name;
|
||||
} else {
|
||||
throw UnimplementedError('(${nameNode.runtimeType}) $nameNode');
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ class FindNode {
|
||||
ConstructorFieldInitializer get singleConstructorFieldInitializer =>
|
||||
_single();
|
||||
|
||||
DotShorthandInvocation get singleDotShorthandInvocation => _single();
|
||||
|
||||
EnumDeclaration get singleEnumDeclaration => _single();
|
||||
|
||||
ExportDirective get singleExportDirective => _single();
|
||||
@@ -360,6 +362,10 @@ class FindNode {
|
||||
return _node(search, (n) => n is DoStatement);
|
||||
}
|
||||
|
||||
DotShorthandInvocation dotShorthandInvocation(String search) {
|
||||
return _node(search, (n) => n is DotShorthandInvocation);
|
||||
}
|
||||
|
||||
DoubleLiteral doubleLiteral(String search) {
|
||||
return _node(search, (n) => n is DoubleLiteral);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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.
|
||||
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'context_collection_resolution.dart';
|
||||
import 'node_text_expectations.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(DotShorthandInvocationResolutionTest);
|
||||
defineReflectiveTests(UpdateNodeTextExpectations);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class DotShorthandInvocationResolutionTest extends PubPackageResolutionTest {
|
||||
test_dotShorthand_basic() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
class C {
|
||||
static C member() => C(1);
|
||||
int x;
|
||||
C(this.x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
C c = .member();
|
||||
print(c);
|
||||
}
|
||||
''');
|
||||
|
||||
var node = findNode.singleDotShorthandInvocation;
|
||||
assertResolvedNodeText(node, r'''
|
||||
DotShorthandInvocation
|
||||
period: .
|
||||
memberName: SimpleIdentifier
|
||||
token: member
|
||||
element: <testLibraryFragment>::@class::C::@method::member#element
|
||||
staticType: C Function()
|
||||
argumentList: ArgumentList
|
||||
leftParenthesis: (
|
||||
rightParenthesis: )
|
||||
staticInvokeType: C Function()
|
||||
staticType: C
|
||||
''');
|
||||
}
|
||||
|
||||
test_dotShorthand_basic_generic() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
class C<T> {
|
||||
static C member<U>(U x) => C(x);
|
||||
T x;
|
||||
C(this.x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
C c = .member<int>(1);
|
||||
print(c);
|
||||
}
|
||||
''');
|
||||
|
||||
var node = findNode.singleDotShorthandInvocation;
|
||||
assertResolvedNodeText(node, r'''
|
||||
DotShorthandInvocation
|
||||
period: .
|
||||
memberName: SimpleIdentifier
|
||||
token: member
|
||||
element: <testLibraryFragment>::@class::C::@method::member#element
|
||||
staticType: C<dynamic> Function<U>(U)
|
||||
typeArguments: TypeArgumentList
|
||||
leftBracket: <
|
||||
arguments
|
||||
NamedType
|
||||
name: int
|
||||
element2: dart:core::@class::int
|
||||
type: int
|
||||
rightBracket: >
|
||||
argumentList: ArgumentList
|
||||
leftParenthesis: (
|
||||
arguments
|
||||
IntegerLiteral
|
||||
literal: 1
|
||||
correspondingParameter: ParameterMember
|
||||
baseElement: <testLibraryFragment>::@class::C::@method::member::@parameter::x#element
|
||||
substitution: {U: int}
|
||||
staticType: int
|
||||
rightParenthesis: )
|
||||
staticInvokeType: C<dynamic> Function(int)
|
||||
staticType: C<dynamic>
|
||||
typeArgumentTypes
|
||||
int
|
||||
''');
|
||||
}
|
||||
|
||||
test_dotShorthand_basic_parameter() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
class C {
|
||||
static C member(int x) => C(x);
|
||||
int x;
|
||||
C(this.x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
C c = .member(1);
|
||||
print(c);
|
||||
}
|
||||
''');
|
||||
|
||||
var node = findNode.singleDotShorthandInvocation;
|
||||
assertResolvedNodeText(node, r'''
|
||||
DotShorthandInvocation
|
||||
period: .
|
||||
memberName: SimpleIdentifier
|
||||
token: member
|
||||
element: <testLibraryFragment>::@class::C::@method::member#element
|
||||
staticType: C Function(int)
|
||||
argumentList: ArgumentList
|
||||
leftParenthesis: (
|
||||
arguments
|
||||
IntegerLiteral
|
||||
literal: 1
|
||||
correspondingParameter: <testLibraryFragment>::@class::C::@method::member::@parameter::x#element
|
||||
staticType: int
|
||||
rightParenthesis: )
|
||||
staticInvokeType: C Function(int)
|
||||
staticType: C
|
||||
''');
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import 'constructor_field_initializer_test.dart'
|
||||
import 'constructor_reference_test.dart' as constructor_reference;
|
||||
import 'constructor_test.dart' as constructor;
|
||||
import 'declared_variable_pattern_test.dart' as declared_variable_pattern;
|
||||
import 'dot_shorthand_invocation_test.dart' as dot_shorthand_invocation;
|
||||
import 'enum_test.dart' as enum_resolution;
|
||||
import 'extension_method_test.dart' as extension_method;
|
||||
import 'extension_override_test.dart' as extension_override;
|
||||
@@ -139,6 +140,7 @@ main() {
|
||||
constructor_reference.main();
|
||||
constructor.main();
|
||||
declared_variable_pattern.main();
|
||||
dot_shorthand_invocation.main();
|
||||
enum_resolution.main();
|
||||
extension_method.main();
|
||||
extension_override.main();
|
||||
|
||||
@@ -34,13 +34,13 @@ main() {
|
||||
|
||||
test_dotShorthands_disabled() async {
|
||||
await assertErrorsInCode(r'''
|
||||
// @dart = 3.8
|
||||
void main() {
|
||||
Object c = .hash;
|
||||
Object c = .hash(1, 2);
|
||||
print(c);
|
||||
}
|
||||
''', [
|
||||
error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 27, 1),
|
||||
error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 28, 4),
|
||||
error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 42, 1),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -903,6 +903,28 @@ ConstructorDeclaration
|
||||
''');
|
||||
}
|
||||
|
||||
void test_dotShorthand_invocation() {
|
||||
var parseResult = parseStringWithErrors(r'''
|
||||
class C {}
|
||||
|
||||
void main() {
|
||||
C c = .new();
|
||||
}
|
||||
''');
|
||||
parseResult.assertNoErrors();
|
||||
|
||||
var node = parseResult.findNode.dotShorthandInvocation('.new()');
|
||||
assertParsedNodeText(node, r'''
|
||||
DotShorthandInvocation
|
||||
period: .
|
||||
memberName: SimpleIdentifier
|
||||
token: new
|
||||
argumentList: ArgumentList
|
||||
leftParenthesis: (
|
||||
rightParenthesis: )
|
||||
''');
|
||||
}
|
||||
|
||||
void test_enum_base() {
|
||||
var parseResult = parseStringWithErrors(r'''
|
||||
base enum E { v }
|
||||
|
||||
@@ -465,6 +465,18 @@ class ResolvedAstPrinter extends ThrowingAstVisitor<void> {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDotShorthandInvocation(DotShorthandInvocation node) {
|
||||
_sink.writeln('DotShorthandInvocation');
|
||||
_sink.withIndent(() {
|
||||
_writeNamedChildEntities(node);
|
||||
_writeParameterElement(node);
|
||||
_writeType('staticInvokeType', node.staticInvokeType);
|
||||
_writeType('staticType', node.staticType);
|
||||
_writeTypeList('typeArgumentTypes', node.typeArgumentTypes);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDottedName(DottedName node) {
|
||||
_sink.writeln('DottedName');
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:analyzer/dart/analysis/features.dart';
|
||||
/// whether a given flag is already included.
|
||||
List<String> experimentsForTests = [
|
||||
Feature.augmentations.enableString,
|
||||
Feature.dot_shorthands.enableString,
|
||||
Feature.enhanced_parts.enableString,
|
||||
Feature.macros.enableString,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user