Skip to content

Commit df80d6c

Browse files
scosenzaSteve Cosenza
authored andcommitted
Improve jdeps logic to find more explicit and implicit dependencies (bazelbuild#1118)
* Improve Kotlin jdeps logic to find more explicit and implicit dependencies (bazelbuild#17) # Problem Kotlin jdeps do not collect many direct and indirect dependencies that are required for compilation. These missing jdep found dependencies were discovered as Airbnb tests a "jdeps compile-time pruning" optimization which provides significant IntelliJ indexing, UI, and compilation performance benefits in our fork of the [Bazel IntelliJ Plugin](https://github.com/bazelbuild/intellij). # Solution As a followup to bazelbuild#1045, write test cases to repro missing dependencies found in Airbnb's monorepo, and then fix the underlying issues. In addition to these fixes helping our "jdeps compile-time pruning" optimization, these fixes should also help when the normal Bazel IntelliJ Plugin is used, as it also consults jdeps when deciding what jars to import into IntelliJ. # Test Plan: * Wrote Kotlin and Java test cases to repro missing dependencies found in the Airbnb monorepo * Updated Airbnb's minimal rules_kotlin fork to use the changes in this PR, which has been successfully running CI for over a week. * formatting * formatting * revert * Remove println used for testing * use new assertion style in jdeps test * add newline to trigger another build * Remove class in "js" package, that shouldn't be used and also is no longer needed. * Remove class in "js" package, that shouldn't be used and also is no longer needed. --------- Co-authored-by: Steve Cosenza <[email protected]>
1 parent 6ff1cc3 commit df80d6c

24 files changed

+539
-54
lines changed

src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenExtension.kt

Lines changed: 129 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,45 @@ import org.jetbrains.kotlin.analyzer.AnalysisResult
88
import org.jetbrains.kotlin.config.CompilerConfiguration
99
import org.jetbrains.kotlin.container.StorageComponentContainer
1010
import org.jetbrains.kotlin.container.useInstance
11+
import org.jetbrains.kotlin.descriptors.CallableDescriptor
1112
import org.jetbrains.kotlin.descriptors.ClassDescriptor
1213
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
1314
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
1415
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
1516
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
16-
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
1717
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
1818
import org.jetbrains.kotlin.descriptors.SourceElement
19+
import org.jetbrains.kotlin.descriptors.ValueDescriptor
1920
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
2021
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
21-
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
22-
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
22+
import org.jetbrains.kotlin.load.java.descriptors.JavaClassConstructorDescriptor
2323
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
2424
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
2525
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaField
26+
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartSource
2627
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
2728
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
2829
import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
2930
import org.jetbrains.kotlin.platform.TargetPlatform
3031
import org.jetbrains.kotlin.psi.KtDeclaration
3132
import org.jetbrains.kotlin.psi.KtFile
3233
import org.jetbrains.kotlin.resolve.BindingTrace
33-
import org.jetbrains.kotlin.resolve.FunctionImportedFromObject
34-
import org.jetbrains.kotlin.resolve.PropertyImportedFromObject
34+
import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor
3535
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
3636
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
37+
import org.jetbrains.kotlin.resolve.calls.model.ExpressionKotlinCallArgument
3738
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
38-
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
39+
import org.jetbrains.kotlin.resolve.calls.tower.NewAbstractResolvedCall
40+
import org.jetbrains.kotlin.resolve.calls.tower.PSIKotlinCallArgument
3941
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
4042
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
43+
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
4144
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
45+
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedTypeAliasDescriptor
46+
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
4247
import org.jetbrains.kotlin.types.KotlinType
4348
import org.jetbrains.kotlin.types.TypeConstructor
49+
import org.jetbrains.kotlin.types.getAbbreviation
4450
import org.jetbrains.kotlin.types.typeUtil.supertypes
4551
import java.io.BufferedOutputStream
4652
import java.io.File
@@ -77,7 +83,22 @@ class JdepsGenExtension(
7783
* @return the path corresponding to the JAR where this class was loaded from, or null.
7884
*/
7985
fun getClassCanonicalPath(descriptor: DeclarationDescriptorWithSource): String? {
80-
return when (val sourceElement: SourceElement = descriptor.source) {
86+
// Get the descriptor's source element which may be a type alias
87+
val sourceElement = if (descriptor.source != SourceElement.NO_SOURCE) {
88+
descriptor.source
89+
} else {
90+
when (val declarationDescriptor = descriptor.getImportableDescriptor()) {
91+
is DeserializedTypeAliasDescriptor -> {
92+
declarationDescriptor.containerSource
93+
}
94+
95+
else -> {
96+
null
97+
}
98+
}
99+
}
100+
101+
return when (sourceElement) {
81102
is JavaSourceElement ->
82103
if (sourceElement.javaElement is BinaryJavaClass) {
83104
(sourceElement.javaElement as BinaryJavaClass).virtualFile.canonicalPath
@@ -92,13 +113,21 @@ class JdepsGenExtension(
92113
// Ignore Java source local to this module.
93114
null
94115
}
116+
95117
is KotlinJvmBinarySourceElement ->
96118
(sourceElement.binaryClass as VirtualFileKotlinClass).file.canonicalPath
97-
else -> null
119+
120+
is JvmPackagePartSource -> { // This case is needed to collect type aliases
121+
((sourceElement.knownJvmBinaryClass) as VirtualFileKotlinClass).file.canonicalPath
122+
}
123+
124+
else -> {
125+
null
126+
}
98127
}
99128
}
100129

101-
fun getClassCanonicalPath(typeConstructor: TypeConstructor): String? {
130+
private fun getClassCanonicalPath(typeConstructor: TypeConstructor): String? {
102131
return (typeConstructor.declarationDescriptor as? DeclarationDescriptorWithSource)?.let {
103132
getClassCanonicalPath(
104133
it,
@@ -130,54 +159,88 @@ class JdepsGenExtension(
130159
reportOn: PsiElement,
131160
context: CallCheckerContext,
132161
) {
133-
when (val resultingDescriptor = resolvedCall.resultingDescriptor) {
134-
is FunctionImportedFromObject -> {
135-
collectTypeReferences(resultingDescriptor.containingObject.defaultType)
136-
}
137-
is PropertyImportedFromObject -> {
138-
collectTypeReferences(resultingDescriptor.containingObject.defaultType)
162+
// First collect type from the Resolved Call
163+
collectExplicitTypes(resolvedCall)
164+
165+
resolvedCall.valueArguments.keys.forEach { valueArgument ->
166+
collectTypeReferences(valueArgument.type, isExplicit = false)
167+
}
168+
169+
// And then collect types from any ResultingDescriptor
170+
val resultingDescriptor = resolvedCall.resultingDescriptor
171+
collectExplicitTypes(resultingDescriptor)
172+
173+
val isExplicitReturnType = resultingDescriptor is JavaClassConstructorDescriptor
174+
resultingDescriptor.returnType?.let {
175+
collectTypeReferences(it, isExplicit = isExplicitReturnType, collectTypeArguments = false)
176+
}
177+
178+
resultingDescriptor.valueParameters.forEach { valueParameter ->
179+
collectTypeReferences(valueParameter.type, isExplicit = false)
180+
}
181+
182+
// Finally, collect types that depend on the type of the ResultingDescriptor and note that
183+
// these descriptors may be composed of multiple classes that we need to extract types from
184+
if (resultingDescriptor is DeclarationDescriptor) {
185+
val containingDeclaration = resultingDescriptor.containingDeclaration
186+
if (containingDeclaration is ClassDescriptor) {
187+
collectTypeReferences(containingDeclaration.defaultType)
139188
}
140-
is JavaMethodDescriptor -> {
141-
getClassCanonicalPath(
142-
(resultingDescriptor.containingDeclaration as ClassDescriptor).typeConstructor,
143-
)?.let { explicitClassesCanonicalPaths.add(it) }
189+
190+
if (resultingDescriptor is PropertyDescriptor) {
191+
(
192+
resultingDescriptor.getter
193+
?.correspondingProperty as? SyntheticJavaPropertyDescriptor
194+
)
195+
?.let { syntheticJavaPropertyDescriptor ->
196+
collectTypeReferences(syntheticJavaPropertyDescriptor.type, isExplicit = false)
197+
198+
val functionDescriptor = syntheticJavaPropertyDescriptor.getMethod
199+
functionDescriptor.dispatchReceiverParameter?.type?.let { dispatchReceiverType ->
200+
collectTypeReferences(dispatchReceiverType, isExplicit = false)
201+
}
202+
}
144203
}
145-
is FunctionDescriptor -> {
146-
resultingDescriptor.returnType?.let {
147-
collectTypeReferences(it, isExplicit = false, collectTypeArguments = false)
148-
}
149-
resultingDescriptor.valueParameters.forEach { valueParameter ->
150-
collectTypeReferences(valueParameter.type, isExplicit = false)
204+
}
205+
206+
if (resultingDescriptor is ImportedFromObjectCallableDescriptor<*>) {
207+
collectTypeReferences(resultingDescriptor.containingObject.defaultType, isExplicit = false)
208+
}
209+
210+
if (resultingDescriptor is ValueDescriptor) {
211+
collectTypeReferences(resultingDescriptor.type, isExplicit = false)
212+
}
213+
}
214+
215+
private fun collectExplicitTypes(resultingDescriptor: CallableDescriptor) {
216+
val kotlinJvmBinaryClass = resultingDescriptor.getContainingKotlinJvmBinaryClass()
217+
if (kotlinJvmBinaryClass is VirtualFileKotlinClass) {
218+
explicitClassesCanonicalPaths.add(kotlinJvmBinaryClass.file.path)
219+
}
220+
}
221+
222+
private fun collectExplicitTypes(resolvedCall: ResolvedCall<*>) {
223+
val kotlinCallArguments = (resolvedCall as? NewAbstractResolvedCall)
224+
?.resolvedCallAtom?.atom?.argumentsInParenthesis
225+
226+
// note that callArgument can be both a PSIKotlinCallArgument and an ExpressionKotlinCallArgument
227+
kotlinCallArguments?.forEach { callArgument ->
228+
if (callArgument is PSIKotlinCallArgument) {
229+
val dataFlowInfos = listOf(callArgument.dataFlowInfoBeforeThisArgument) +
230+
callArgument.dataFlowInfoAfterThisArgument
231+
232+
dataFlowInfos.forEach { dataFlowInfo ->
233+
dataFlowInfo.completeTypeInfo.values().flatten().forEach { kotlinType ->
234+
collectTypeReferences(kotlinType, isExplicit = true)
235+
}
151236
}
152-
val virtualFileClass =
153-
resultingDescriptor.getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass
154-
?: return
155-
explicitClassesCanonicalPaths.add(virtualFileClass.file.path)
156-
}
157-
is ParameterDescriptor -> {
158-
getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) }
159-
}
160-
is FakeCallableDescriptorForObject -> {
161-
collectTypeReferences(resultingDescriptor.type)
162237
}
163-
is JavaPropertyDescriptor -> {
164-
getClassCanonicalPath(resultingDescriptor)?.let { explicitClassesCanonicalPaths.add(it) }
165-
}
166-
is PropertyDescriptor -> {
167-
when (resultingDescriptor.containingDeclaration) {
168-
is ClassDescriptor -> collectTypeReferences(
169-
(resultingDescriptor.containingDeclaration as ClassDescriptor).defaultType,
170-
)
171-
else -> {
172-
val virtualFileClass =
173-
(resultingDescriptor).getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass
174-
?: return
175-
explicitClassesCanonicalPaths.add(virtualFileClass.file.path)
176-
}
238+
239+
if (callArgument is ExpressionKotlinCallArgument) {
240+
callArgument.receiver.allOriginalTypes.forEach { kotlinType ->
241+
collectTypeReferences(kotlinType, isExplicit = true)
177242
}
178-
collectTypeReferences(resultingDescriptor.type, isExplicit = false)
179243
}
180-
else -> return
181244
}
182245
}
183246

@@ -192,8 +255,9 @@ class JdepsGenExtension(
192255
collectTypeReferences(it)
193256
}
194257
}
258+
195259
is FunctionDescriptor -> {
196-
descriptor.returnType?.let { collectTypeReferences(it) }
260+
descriptor.returnType?.let { collectTypeReferences(it, collectTypeArguments = false) }
197261
descriptor.valueParameters.forEach { valueParameter ->
198262
collectTypeReferences(valueParameter.type)
199263
}
@@ -204,6 +268,7 @@ class JdepsGenExtension(
204268
collectTypeReferences(it)
205269
}
206270
}
271+
207272
is PropertyDescriptor -> {
208273
collectTypeReferences(descriptor.type)
209274
descriptor.annotations.forEach { annotation ->
@@ -213,6 +278,7 @@ class JdepsGenExtension(
213278
collectTypeReferences(annotation.type)
214279
}
215280
}
281+
216282
is LocalVariableDescriptor -> {
217283
collectTypeReferences(descriptor.type)
218284
}
@@ -245,6 +311,17 @@ class JdepsGenExtension(
245311
}
246312
}
247313

314+
// Collect type aliases aka abbreviations
315+
// See: https://github.com/Kotlin/KEEP/blob/master/proposals/type-aliases.md#type-alias-expansion
316+
kotlinType.getAbbreviation()?.let { abbreviationType ->
317+
collectTypeReferences(
318+
abbreviationType,
319+
isExplicit = isExplicit,
320+
collectTypeArguments = collectTypeArguments,
321+
visitedKotlinTypes = visitedKotlinTypes,
322+
)
323+
}
324+
248325
kotlinType.supertypes().forEach { supertype ->
249326
collectTypeReferences(
250327
supertype,

src/test/kotlin/io/bazel/kotlin/builder/tasks/BUILD.bazel

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ java_library(
1919
name = "JdepsParserTestFixtures",
2020
testonly = True,
2121
srcs = glob(["testFixtures/*.java"]),
22+
deps = [
23+
":JdepsParserTestFixtures2",
24+
],
25+
)
26+
27+
java_library(
28+
name = "JdepsParserTestFixtures2",
29+
testonly = True,
30+
srcs = glob(["testFixtures2/*.java"]),
2231
)
2332

2433
kt_rules_test(
@@ -44,7 +53,10 @@ kt_rules_test(
4453
name = "KotlinBuilderJvmJdepsTest",
4554
size = "large",
4655
srcs = ["jvm/KotlinBuilderJvmJdepsTest.kt"],
47-
data = [":JdepsParserTestFixtures"],
56+
data = [
57+
":JdepsParserTestFixtures",
58+
":JdepsParserTestFixtures2",
59+
],
4860
)
4961

5062
kt_rules_test(

0 commit comments

Comments
 (0)