| 
 | 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