1 /* <lambda>null2 * Copyright (C) 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 com.android.tools.metalava.model.turbine 18 19 import com.android.tools.lint.checks.infrastructure.TestFile 20 import com.android.tools.metalava.testing.TemporaryFolderOwner 21 import com.android.tools.metalava.testing.java 22 import com.google.common.collect.ImmutableList 23 import com.google.turbine.binder.Binder 24 import com.google.turbine.binder.ClassPathBinder 25 import com.google.turbine.binder.JimageClassBinder 26 import com.google.turbine.binder.bound.SourceTypeBoundClass 27 import com.google.turbine.binder.bound.TypeBoundClass 28 import com.google.turbine.binder.env.CompoundEnv 29 import com.google.turbine.binder.env.SimpleEnv 30 import com.google.turbine.binder.sym.ClassSymbol 31 import com.google.turbine.diag.SourceFile 32 import com.google.turbine.parse.Parser 33 import com.google.turbine.tree.Tree 34 import com.google.turbine.tree.Tree.Ident 35 import java.util.Optional 36 import kotlin.test.fail 37 import org.junit.Rule 38 import org.junit.Test 39 import org.junit.rules.TemporaryFolder 40 41 class TurbineFieldResolverTest : TemporaryFolderOwner { 42 @get:Rule override val temporaryFolder = TemporaryFolder() 43 44 /** Parse and bind [sources] into a [CompoundEnv]. */ 45 private fun bind(sources: List<TestFile>): CompoundEnv<ClassSymbol, TypeBoundClass> { 46 val srcDir = temporaryFolder.newFolder("src") 47 48 // Parse all the files. 49 val units = 50 sources 51 .stream() 52 .map { it.createFile(srcDir) } 53 .map { file -> Parser.parse(SourceFile(file.path, file.readText())) } 54 .collect(ImmutableList.toImmutableList()) 55 56 // Bind them together. 57 val classPath = ClassPathBinder.bindClasspath(listOf()) 58 val bootClassPath = JimageClassBinder.bindDefault() 59 val result = 60 Binder.bind(units, classPath, bootClassPath, /* moduleVersion= */ Optional.empty()) 61 ?: error("Binding failed") 62 63 // Get mapping from ClassSymbol to TypeBoundClass from the class path. 64 val classPathEnv: CompoundEnv<ClassSymbol, TypeBoundClass> = 65 CompoundEnv.of(result.classPathEnv()) 66 67 // Get mapping from ClassSymbol to SourceTypeBoundClass from sources. 68 val sourceEnv = SimpleEnv(result.units()) 69 70 // Combine the mappings together. Searching sources first then class path. 71 val combinedEnv = classPathEnv.append(sourceEnv) 72 return combinedEnv 73 } 74 75 /** Create a [TurbineFieldResolver] for resolving fields as if from within [binaryClassName]. */ 76 private fun CompoundEnv<ClassSymbol, TypeBoundClass>.resolverFor( 77 binaryClassName: String 78 ): TurbineFieldResolver { 79 // Select the class from where the field will be resolved. 80 val testClassSym = ClassSymbol(binaryClassName) 81 val testClassInfo = 82 this[testClassSym] as? SourceTypeBoundClass ?: error("unknown class $testClassSym") 83 84 // Create a resolver. 85 val fieldResolver = 86 TurbineFieldResolver( 87 testClassSym, 88 testClassSym, 89 testClassInfo.memberImports(), 90 testClassInfo.scope(), 91 this 92 ) 93 return fieldResolver 94 } 95 96 private fun assertFieldCanBeResolved(fieldResolver: TurbineFieldResolver, fieldName: String) { 97 val idents = 98 fieldName 99 .split(".") 100 .stream() 101 .map { Ident(1, it) } 102 .collect(ImmutableList.toImmutableList()) 103 104 fieldResolver.resolveField(Tree.ConstVarName(0, idents)) 105 ?: fail("Could not resolve field $fieldName within $fieldResolver") 106 } 107 108 @Test 109 fun `Test basic resolver`() { 110 val sources = 111 listOf( 112 java( 113 """ 114 package test.pkg; 115 import test.other.pkg.Imported; 116 import static test.other.pkg.ImportedStatically.STATIC; 117 import test.wildcard.pkg.*; 118 import static test.wildcard.pkg.ImportedWildcardStatically.*; 119 public class Test { 120 public static int instanceField = 1; 121 public static final int STATIC_FIELD = 2; 122 public class Nested { 123 public static final int NESTED_FIELD = 3; 124 } 125 } 126 """ 127 ), 128 java( 129 """ 130 package test.other.pkg; 131 public enum NotImported { 132 ENUM1, 133 ENUM2, 134 } 135 """ 136 ), 137 java( 138 """ 139 package test.other.pkg; 140 public enum Imported { 141 CONST, 142 } 143 """ 144 ), 145 java( 146 """ 147 package test.other.pkg; 148 public enum ImportedStatically { 149 STATIC, 150 } 151 """ 152 ), 153 java( 154 """ 155 package test.wildcard.pkg; 156 public enum ImportedWildcard { 157 WILDCARD, 158 } 159 """ 160 ), 161 java( 162 """ 163 package test.wildcard.pkg; 164 public enum ImportedWildcardStatically { 165 STATIC_WILDCARD, 166 } 167 """ 168 ), 169 ) 170 171 val combinedEnv = bind(sources) 172 173 combinedEnv.resolverFor("test/pkg/Test").let { fieldResolver -> 174 assertFieldCanBeResolved(fieldResolver, "instanceField") 175 assertFieldCanBeResolved(fieldResolver, "STATIC_FIELD") 176 assertFieldCanBeResolved(fieldResolver, "Nested.NESTED_FIELD") 177 assertFieldCanBeResolved(fieldResolver, "Float.NaN") 178 assertFieldCanBeResolved(fieldResolver, "test.other.pkg.NotImported.ENUM1") 179 assertFieldCanBeResolved(fieldResolver, "Imported.CONST") 180 assertFieldCanBeResolved(fieldResolver, "STATIC") 181 assertFieldCanBeResolved(fieldResolver, "ImportedWildcard.WILDCARD") 182 assertFieldCanBeResolved(fieldResolver, "STATIC_WILDCARD") 183 } 184 185 combinedEnv.resolverFor("test/pkg/Test${"$"}Nested").let { fieldResolver -> 186 assertFieldCanBeResolved(fieldResolver, "instanceField") 187 assertFieldCanBeResolved(fieldResolver, "STATIC_FIELD") 188 } 189 } 190 } 191