• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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