1 /*
<lambda>null2  * Copyright 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.lint
18 
19 import com.android.tools.lint.detector.api.Category
20 import com.android.tools.lint.detector.api.Detector
21 import com.android.tools.lint.detector.api.Implementation
22 import com.android.tools.lint.detector.api.Issue
23 import com.android.tools.lint.detector.api.JavaContext
24 import com.android.tools.lint.detector.api.Scope
25 import com.android.tools.lint.detector.api.Severity
26 import com.android.tools.lint.detector.api.SourceCodeScanner
27 import java.util.EnumSet
28 import org.jetbrains.kotlin.analysis.api.analyze
29 import org.jetbrains.kotlin.psi.KtClass
30 import org.jetbrains.uast.UClass
31 
32 /**
33  * Lint [Detector] to warn for ModifierNodeElement implementations that have lambda properties and
34  * use a data class to implement equality. This will use structural equality to compare lambdas,
35  * which can cause issues for function references: see [LambdaStructuralEqualityDetector].
36  */
37 class ModifierNodeElementDataClassWithLambdaDetector : Detector(), SourceCodeScanner {
38     override fun applicableSuperClasses(): List<String> =
39         listOf(Names.Ui.Node.ModifierNodeElement.javaFqn)
40 
41     override fun visitClass(context: JavaContext, declaration: UClass) {
42         val ktClass = declaration.sourcePsi as? KtClass ?: return
43         if (!ktClass.isData()) return
44         val constructor = ktClass.primaryConstructor
45         // Just warn on the first lambda, to avoid duplicate warnings
46         val lambdaParameter =
47             constructor?.valueParameters?.find { parameter ->
48                 parameter.typeReference?.let { typeReference ->
49                     analyze(typeReference) { typeReference.type.isFunctionType }
50                 } == true
51             }
52         lambdaParameter?.let {
53             context.report(
54                 ISSUE,
55                 lambdaParameter,
56                 context.getLocation(lambdaParameter),
57                 BriefDescription
58             )
59         }
60     }
61 
62     companion object {
63         private const val BriefDescription =
64             "ModifierNodeElement implementations using a data class with lambda properties will result in incorrect equals implementations"
65         private const val Explanation =
66             "ModifierNodeElement implementations using a data class with lambda properties will " +
67                 "result in equals implementations that check the structural equality of lambdas. " +
68                 "Checking structural equality on lambdas can lead to issues, as function " +
69                 "references (::lambda) do not consider their capture scope in their equals " +
70                 "implementation. This means that structural equality can return true, even if " +
71                 "the lambdas are different references with a different capture scope. Instead " +
72                 "of using a data class to implement ModifierNodeElement with lambda properties, " +
73                 "you should instead manually implement equals / hashcode."
74 
75         val ISSUE =
76             Issue.create(
77                 "ModifierNodeElementDataClassWithLambda",
78                 BriefDescription,
79                 Explanation,
80                 Category.CORRECTNESS,
81                 5,
82                 Severity.ERROR,
83                 Implementation(
84                     ModifierNodeElementDataClassWithLambdaDetector::class.java,
85                     // Too noisy for tests, and unlikely to cause issues
86                     EnumSet.of(Scope.JAVA_FILE)
87                 )
88             )
89     }
90 }
91