Skip to content

Commit 38752ca

Browse files
kallentuCommit Queue
authored andcommitted
[analyzer] Dot shorthands: Utility function to find if a node needs to keep its declared type for a shorthand.
Adds a helper to use for lints and fixes for finding whether the AST node contains a dot shorthand that relies on a declared type (in the parent of that node). The algorithm of `hasDependentDotShorthand` looks at the following: 0. If the node is a dot shorthand, return true. 1. Check that the node is a generic function, a list/map/set literal or a generic function expression -- with no explicit type arguments provided. For method invocations: 2. Note which type parameters are used in the return type -- these are the type parameters that depend on type inference. 3. Check the parameters that have type parameters in the above set. 4. If the arguments to those parameters have dot shorthands (recursively checked), then return true. For list/set/maps: 2. Recurse with each key and value, checking if there's a dependent dot shorthand. For function expressions: 2. Check each return statement to see if there's a dependent dot shorthand. For constructor invocations: 2. Make sure there are no explicit type arguments given. 3. Check each argument with type parameters in its parameter to see if there's a dependent dot shorthand. Bug: #59835 Change-Id: Iaed5fbb2dc60ee7498c1bb995272da288e306b5e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/440080 Commit-Queue: Kallen Tu <[email protected]> Reviewed-by: Chloe Stefantsova <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 7684ae5 commit 38752ca

File tree

3 files changed

+493
-0
lines changed

3 files changed

+493
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:analyzer/dart/element/type.dart';
7+
import 'package:analyzer/src/dart/ast/ast.dart';
8+
import 'package:analyzer/src/dart/element/type_visitor.dart';
9+
10+
/// Whether the expression is a dot shorthand or has a dot shorthand in its
11+
/// arguments that relies on type inference.
12+
bool hasDependentDotShorthand(AstNode node) {
13+
if (node case DotShorthandMixin(
14+
isDotShorthand: true,
15+
:var correspondingParameter,
16+
)) {
17+
// There's no corresponding parameter, so we rely on the type provided by
18+
// the for-loop or variable declaration.
19+
if (correspondingParameter == null) return true;
20+
21+
// The type used to infer the dot shorthand is a type parameter. We need
22+
// to avoid reporting a lint here.
23+
if (correspondingParameter.baseElement.type is TypeParameterType) {
24+
return true;
25+
}
26+
} else if (node case MethodInvocation(
27+
methodName: SimpleIdentifier(:FunctionType staticType),
28+
typeArguments: null,
29+
argumentList: ArgumentList(:var arguments),
30+
)) {
31+
// When the static type of the method invocation is a generic function type
32+
// with no explicit type arguments given, we will be inferring those types.
33+
var typeParameters = staticType.typeParameters;
34+
if (typeParameters.isEmpty) return false;
35+
36+
// The type parameters that are dependent on type inference are in the
37+
// return type. We populate those type parameters.
38+
//
39+
// As an optimization, we filter the type parameters to only include the
40+
// type parameters declared on the method invocation.
41+
var returnType = staticType.returnType;
42+
var dependentTypeParameters = _findTypeParametersForType(
43+
returnType,
44+
).where((element) => typeParameters.contains(element));
45+
if (dependentTypeParameters.isEmpty) return false;
46+
47+
// Then looking at every argument in the method invocation, we recursively
48+
// check the arguments of parameters that have type parameters that are in
49+
// the set of dependent type parameters that we calculated above.
50+
for (var argument in arguments) {
51+
var parameterTypeParameters = _findTypeParametersForFormalParameter(
52+
argument.correspondingParameter,
53+
);
54+
if (parameterTypeParameters.isEmpty) continue;
55+
56+
if (parameterTypeParameters.any(
57+
(type) => dependentTypeParameters.contains(type),
58+
)) {
59+
if (hasDependentDotShorthand(argument)) return true;
60+
}
61+
}
62+
} else if (node
63+
case ListLiteral(typeArguments: null, :var elements) ||
64+
SetOrMapLiteral(typeArguments: null, :var elements)) {
65+
// Lists, maps, and sets that have inferred type arguments need their
66+
// elements verified for dot shorthands that depend on that type inference.
67+
for (var element in elements) {
68+
if (element is MapLiteralEntry) {
69+
if (hasDependentDotShorthand(element.key) ||
70+
hasDependentDotShorthand(element.value)) {
71+
return true;
72+
}
73+
} else if (hasDependentDotShorthand(element)) {
74+
return true;
75+
}
76+
}
77+
} else if (node case FunctionExpression(:var body)) {
78+
// Check if the return statement(s) of the function expression have a
79+
// dependent dot shorthand.
80+
switch (body) {
81+
case ExpressionFunctionBody(:var expression):
82+
return hasDependentDotShorthand(expression);
83+
case BlockFunctionBody(block: Block(:var statements)):
84+
for (var statement in statements) {
85+
if (statement is ReturnStatement) {
86+
var expression = statement.expression;
87+
if (expression != null && hasDependentDotShorthand(expression)) {
88+
return true;
89+
}
90+
}
91+
}
92+
default:
93+
return false;
94+
}
95+
} else if (node case InstanceCreationExpressionImpl(
96+
constructorName: ConstructorName(:var type),
97+
:var argumentList,
98+
)) {
99+
// Type arguments to the constructor are explicitly given. We know that no
100+
// inference information is required from any parent declared types.
101+
if (type.typeArguments != null) return false;
102+
103+
for (var argument in argumentList.arguments) {
104+
var parameterTypeParameters = _findTypeParametersForFormalParameter(
105+
argument.correspondingParameter,
106+
);
107+
if (parameterTypeParameters.isEmpty) continue;
108+
if (hasDependentDotShorthand(argument)) return true;
109+
}
110+
}
111+
return false;
112+
}
113+
114+
/// Finds and returns all the type parameter elements in the formal parameter,
115+
/// [parameter].
116+
Set<TypeParameterElement> _findTypeParametersForFormalParameter(
117+
FormalParameterElement? parameter,
118+
) {
119+
if (parameter == null) return {};
120+
return _findTypeParametersForType(parameter.baseElement.type);
121+
}
122+
123+
/// Finds and returns all the type parameter elements in [type].
124+
Set<TypeParameterElement> _findTypeParametersForType(DartType type) {
125+
var typeParameterVisitor = _TypeParameterVisitor();
126+
type.accept(typeParameterVisitor);
127+
return typeParameterVisitor.typeParameters;
128+
}
129+
130+
class _TypeParameterVisitor extends RecursiveTypeVisitor {
131+
Set<TypeParameterElement> typeParameters = {};
132+
133+
_TypeParameterVisitor() : super(includeTypeAliasArguments: false);
134+
135+
@override
136+
bool visitTypeParameterType(TypeParameterType type) {
137+
typeParameters.add(type.element);
138+
return true;
139+
}
140+
}

0 commit comments

Comments
 (0)