blob: 2f4dd0aaad69548b2aca2431b89e951d5e90a7f6 [file] [log] [blame] [edit]
// 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:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/type_visitor.dart';
/// Whether the expression is a dot shorthand or has a dot shorthand in its
/// arguments that relies on type inference.
///
/// Note: Use [isDotShorthand] for determining whether the general [node] is a
/// dot shorthand. For this helper, [node] should be a for-loop iterable or a
/// variable initializer that we're attempting to remove a declared type for.
///
/// Example of fixes that use this helper are extract local refactoring and
/// `omit_local_variable_types`.
bool hasDependentDotShorthand(AstNode node) {
if (node case DotShorthandMixin(
isDotShorthand: true,
:var correspondingParameter,
)) {
// There's no corresponding parameter, so we rely on the type provided by
// the for-loop or variable declaration.
if (correspondingParameter == null) return true;
// The type used to infer the dot shorthand is a type parameter. We need
// to avoid reporting a lint here.
if (correspondingParameter.baseElement.type is TypeParameterType) {
return true;
}
} else if (node case MethodInvocation(
methodName: SimpleIdentifier(:FunctionType staticType),
typeArguments: null,
argumentList: ArgumentList(:var arguments),
)) {
// When the static type of the method invocation is a generic function type
// with no explicit type arguments given, we will be inferring those types.
var typeParameters = staticType.typeParameters;
if (typeParameters.isEmpty) return false;
// The type parameters that are dependent on type inference are in the
// return type. We populate those type parameters.
//
// As an optimization, we filter the type parameters to only include the
// type parameters declared on the method invocation.
var returnType = staticType.returnType;
var dependentTypeParameters = _findTypeParametersForType(
returnType,
).where((element) => typeParameters.contains(element));
if (dependentTypeParameters.isEmpty) return false;
// Then looking at every argument in the method invocation, we recursively
// check the arguments of parameters that have type parameters that are in
// the set of dependent type parameters that we calculated above.
for (var argument in arguments) {
var parameterTypeParameters = _findTypeParametersForFormalParameter(
argument.correspondingParameter,
);
if (parameterTypeParameters.isEmpty) continue;
if (parameterTypeParameters.any(
(type) => dependentTypeParameters.contains(type),
)) {
if (hasDependentDotShorthand(argument)) return true;
}
}
} else if (node
case ListLiteral(typeArguments: null, :var elements) ||
SetOrMapLiteral(typeArguments: null, :var elements)) {
// Lists, maps, and sets that have inferred type arguments need their
// elements verified for dot shorthands that depend on that type inference.
for (var element in elements) {
if (element is MapLiteralEntry) {
if (hasDependentDotShorthand(element.key) ||
hasDependentDotShorthand(element.value)) {
return true;
}
} else if (hasDependentDotShorthand(element)) {
return true;
}
}
} else if (node case FunctionExpression(:var body)) {
// Check if the return statement(s) of the function expression have a
// dependent dot shorthand.
switch (body) {
case ExpressionFunctionBody(:var expression):
return hasDependentDotShorthand(expression);
case BlockFunctionBody(block: Block(:var statements)):
for (var statement in statements) {
if (statement is ReturnStatement) {
var expression = statement.expression;
if (expression != null && hasDependentDotShorthand(expression)) {
return true;
}
}
}
default:
return false;
}
} else if (node case InstanceCreationExpressionImpl(
constructorName: ConstructorName(:var type),
:var argumentList,
)) {
// Type arguments to the constructor are explicitly given. We know that no
// inference information is required from any parent declared types.
if (type.typeArguments != null) return false;
for (var argument in argumentList.arguments) {
var parameterTypeParameters = _findTypeParametersForFormalParameter(
argument.correspondingParameter,
);
if (parameterTypeParameters.isEmpty) continue;
if (hasDependentDotShorthand(argument)) return true;
}
}
return false;
}
/// Whether the [node] is a dot shorthand expression that relies on a context
/// type.
bool isDotShorthand(AstNode node) =>
node is DotShorthandMixin && node.isDotShorthand;
/// Finds and returns all the type parameter elements in the formal parameter,
/// [parameter].
Set<TypeParameterElement> _findTypeParametersForFormalParameter(
FormalParameterElement? parameter,
) {
if (parameter == null) return {};
return _findTypeParametersForType(parameter.baseElement.type);
}
/// Finds and returns all the type parameter elements in [type].
Set<TypeParameterElement> _findTypeParametersForType(DartType type) {
var typeParameterVisitor = _TypeParameterVisitor();
type.accept(typeParameterVisitor);
return typeParameterVisitor.typeParameters;
}
class _TypeParameterVisitor extends RecursiveTypeVisitor {
Set<TypeParameterElement> typeParameters = {};
_TypeParameterVisitor() : super(includeTypeAliasArguments: false);
@override
bool visitTypeParameterType(TypeParameterType type) {
typeParameters.add(type.element);
return true;
}
}