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