Skip to content

Commit 9600b4e

Browse files
committed
Refactor generated IR classes caching
1 parent 76dced2 commit 9600b4e

File tree

8 files changed

+142
-161
lines changed

8 files changed

+142
-161
lines changed

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/Utils.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/Caches.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,4 @@ object Caches {
1010
val classReferences by cacheKey<IrClassReferencer, IrClass>()
1111
val functionReferences by cacheKey<IrFunctionReferencer, IrSimpleFunction>()
1212
val propertyReferences by cacheKey<IrPropertyReferencer, IrProperty>()
13-
14-
15-
val mockClasses by cacheKey<IrClass, IrClass>()
16-
val spyClasses by cacheKey<IrClass, IrClass>()
17-
val mockManyClasses by cacheKey<Set<IrClass>, IrClass>()
1813
}

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/transformer/MokkeryRootTransformer.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package dev.mokkery.plugin.ir.transformer
22

33
import dev.mokkery.plugin.Mokkery
4-
import dev.mokkery.plugin.caches
5-
import dev.mokkery.plugin.ir.Caches
64
import dev.mokkery.plugin.ir.IrMokkeryPluginScope
75
import dev.mokkery.plugin.ir.MokkeryIr
86
import dev.mokkery.plugin.ir.applyTransformChildrenVoid
@@ -20,7 +18,6 @@ import dev.mokkery.plugin.ir.transformer.templating.replaceVerify
2018
import dev.mokkery.plugin.ir.transformer.templating.replaceVerifySuspend
2119
import org.jetbrains.kotlin.ir.IrStatement
2220
import org.jetbrains.kotlin.ir.declarations.IrClass
23-
import org.jetbrains.kotlin.ir.declarations.IrFile
2421
import org.jetbrains.kotlin.ir.declarations.IrFunction
2522
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
2623
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -66,13 +63,6 @@ class MokkeryRootTransformer(pluginScope: IrMokkeryPluginScope) : CoreTransforme
6663
}
6764
}
6865

69-
override fun visitFileNew(declaration: IrFile): IrFile {
70-
caches[Caches.mockClasses].clear()
71-
caches[Caches.spyClasses].clear()
72-
caches[Caches.mockManyClasses].clear()
73-
return declaration.applyTransformChildrenVoid()
74-
}
75-
7666
override fun visitModuleFragment(declaration: IrModuleFragment): IrModuleFragment {
7767
val time = TimeSource.Monotonic.markNow()
7868
declaration.transformChildrenVoid()

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/transformer/core/BuildersApi.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ import org.jetbrains.kotlin.ir.builders.IrBuilder
1111
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
1212
import org.jetbrains.kotlin.ir.builders.IrGeneratorContext
1313
import org.jetbrains.kotlin.ir.builders.irGetObject
14+
import org.jetbrains.kotlin.ir.declarations.IrClass
15+
import org.jetbrains.kotlin.ir.declarations.IrFile
16+
import org.jetbrains.kotlin.ir.declarations.name
1417
import org.jetbrains.kotlin.ir.expressions.IrCall
1518
import org.jetbrains.kotlin.ir.expressions.IrExpression
1619
import org.jetbrains.kotlin.ir.expressions.IrVarargElement
1720
import org.jetbrains.kotlin.ir.symbols.IrSymbol
1821
import org.jetbrains.kotlin.ir.types.IrType
1922
import org.jetbrains.kotlin.ir.types.typeWith
23+
import org.jetbrains.kotlin.ir.util.findDeclaration
24+
import org.jetbrains.kotlin.ir.util.kotlinFqName
25+
import org.jetbrains.kotlin.name.Name
2026

2127
context(scope: TransformerScope)
2228
inline fun <T> declarationIrBuilder(
@@ -78,3 +84,36 @@ fun IrBuilderWithScope.irGetMokkeryScopeGlobal(): IrCall {
7884
.requirePropertyGetterOwner("global")
7985
.let { irCall(it) { arguments[0] = irGetObject(scopeCompanion.symbol) } }
8086
}
87+
88+
context(scope: TransformerScope)
89+
inline fun findOrBuildClassInCurrentFile(
90+
nameBase: String,
91+
nameHashSource: List<IrClass> = emptyList(),
92+
builder: (Name) -> IrClass
93+
): IrClass {
94+
val name = generatedClassNameInCurrentFile(nameBase, nameHashSource)
95+
return currentFileValue.findDeclaration<IrClass> { it.name == name } ?: builder(name)
96+
}
97+
98+
context(scope: TransformerScope)
99+
fun generatedClassNameInCurrentFile(base: String, implements: List<IrClass>): Name {
100+
val prefix = when {
101+
implements.size == 1 -> "${implements[0].name}_${base}"
102+
else -> base
103+
}
104+
val hash = implements
105+
.map { it.kotlinFqName.asString() }
106+
.plus(currentFileValue.kotlinFqNameStringWithName())
107+
.hexHashString()
108+
return Name.identifier("${prefix}_${hash}")
109+
}
110+
111+
private fun List<String>.hexHashString(): String {
112+
var hash = 0xcbf29ce484222325UL
113+
forEach {
114+
it.forEach { c -> hash = (hash xor c.code.toULong()) * 0x100000001b3UL }
115+
}
116+
return hash.toString(36)
117+
}
118+
119+
private fun IrFile.kotlinFqNameStringWithName() = kotlinFqName.asString() + "." + this.name

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/transformer/mock/BuildDefaultsExtractorFactory.kt

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import dev.mokkery.plugin.ir.requireSimpleFunctionOwner
2020
import dev.mokkery.plugin.ir.transformer.core.TransformerScope
2121
import dev.mokkery.plugin.ir.transformer.core.addToCurrentFile
2222
import dev.mokkery.plugin.ir.transformer.core.declarationIrBuilder
23+
import dev.mokkery.plugin.ir.transformer.core.findOrBuildClassInCurrentFile
2324
import dev.mokkery.plugin.ir.transformer.core.referenced
2425
import dev.mokkery.plugin.ir.transformer.mock.stubs.irDelegatingConstructorWithStubs
2526
import dev.mokkery.plugin.ir.typeWith
@@ -46,31 +47,36 @@ import org.jetbrains.kotlin.ir.util.primaryConstructor
4647
import org.jetbrains.kotlin.name.Name
4748

4849
context(scope: TransformerScope)
49-
fun buildDefaultsExtractorFactoryIfRequired(
50-
className: Name,
50+
fun findOrBuildDefaultsExtractorFactoryIfRequired(
5151
classesToIntercept: List<IrClass>,
5252
bodyBuilder: IrBlockBodyBuilder,
5353
): IrExpression? {
54-
val defaultsExtractor = buildDefaultsExtractorOrNull(className, classesToIntercept) ?: return null
55-
val defaultsFactoryClass = buildDefaultsFactoryClassWith(className, defaultsExtractor.primaryConstructor!!)
56-
return bodyBuilder.run {
57-
irCallConstructor(defaultsFactoryClass.primaryConstructor!!)
54+
val anyDefaults = classesToIntercept.any { clazz ->
55+
clazz.functions.any { it.parameters.any(IrValueParameter::hasDefaultValue) }
5856
}
57+
if (!anyDefaults) return null
58+
val defaultsExtractor = findOrBuildClassInCurrentFile(
59+
nameBase = "DE",
60+
nameHashSource = classesToIntercept,
61+
builder = { buildDefaultsExtractor(it, classesToIntercept) }
62+
)
63+
val defaultsFactoryClass = findOrBuildClassInCurrentFile(
64+
nameBase = "DEF",
65+
nameHashSource = classesToIntercept,
66+
builder = { buildDefaultsExtractorFactoryClass(it, defaultsExtractor.primaryConstructor!!) }
67+
)
68+
return bodyBuilder.run { irCallConstructor(defaultsFactoryClass.primaryConstructor!!) }
5969
}
6070

6171
context(scope: TransformerScope)
62-
private fun buildDefaultsExtractorOrNull(
63-
className: Name,
72+
private fun buildDefaultsExtractor(
73+
name: Name,
6474
classesToIntercept: List<IrClass>
65-
): IrClass? {
66-
val anyDefaults = classesToIntercept.any { clazz ->
67-
clazz.functions.any { it.parameters.any(IrValueParameter::hasDefaultValue) }
68-
}
69-
if (!anyDefaults) return null
75+
): IrClass {
7076
val throwArgumentsFunction = referenced(MokkeryIr.Function.throwArguments)
7177
val methodWithoutDefaultFunction = referenced(MokkeryIr.Function.methodWithoutDefaultsError)
7278
val irBuiltIns = irBuiltIns
73-
val defaultsExtractorClass = irFactory.buildClass { name = Name.identifier("DE${className.asString()}") }
79+
val defaultsExtractorClass = irFactory.buildClass { this.name = name }
7480
classesToIntercept.forEach(defaultsExtractorClass::copyTypeParametersFrom)
7581
defaultsExtractorClass.addToCurrentFile()
7682
defaultsExtractorClass.createThisReceiverParameter()
@@ -121,11 +127,9 @@ private fun buildDefaultsExtractorOrNull(
121127
}
122128

123129
context(scope: TransformerScope)
124-
private fun buildDefaultsFactoryClassWith(className: Name, constructor: IrConstructor): IrClass {
130+
private fun buildDefaultsExtractorFactoryClass(name: Name, constructor: IrConstructor): IrClass {
125131
val defaultsExtractorFactoryInterface = referenced(MokkeryIr.Class.DefaultsExtractorFactory)
126-
val defaultsExtractorFactoryClassImpl = irFactory.buildClass {
127-
name = Name.identifier("DEF${className.asString()}")
128-
}
132+
val defaultsExtractorFactoryClassImpl = irFactory.buildClass { this.name = name }
129133
defaultsExtractorFactoryClassImpl.superTypes = listOf(
130134
defaultsExtractorFactoryInterface.defaultType,
131135
irBuiltIns.anyType

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/transformer/mock/BuildMockClass.kt

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import dev.mokkery.plugin.ir.transformer.core.referenced
3232
import dev.mokkery.plugin.ir.transformer.core.referencedGetterSymbol
3333
import dev.mokkery.plugin.ir.transformer.mock.stubs.irDelegatingConstructorWithStubs
3434
import dev.mokkery.plugin.ir.typeWith
35-
import dev.mokkery.plugin.randomString
3635
import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder
3736
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
3837
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
@@ -58,17 +57,17 @@ import org.jetbrains.kotlin.ir.util.isClass
5857
import org.jetbrains.kotlin.ir.util.isInterface
5958
import org.jetbrains.kotlin.ir.util.kotlinFqName
6059
import org.jetbrains.kotlin.ir.util.parentAsClass
61-
import org.jetbrains.kotlin.name.FqName
6260
import org.jetbrains.kotlin.name.Name
6361
import org.jetbrains.kotlin.utils.memoryOptimizedMap
6462

6563
context(scope: TransformerScope)
6664
fun buildMockClass(
65+
name: Name,
6766
mokkeryKind: IrMokkeryKind,
6867
classToMock: IrClass,
6968
): IrClass {
7069
val instanceScopeClass = referenced(MokkeryIr.Class.MokkeryInstanceScope)
71-
val mockedClass = irFactory.buildClass { name = classToMock.name.createUniqueMockName(mokkeryKind.name) }
70+
val mockedClass = irFactory.buildClass { this.name = name }
7271
mockedClass.addToCurrentFile()
7372
mockedClass.copyTypeParametersFrom(classToMock)
7473
val typedClassToMock = classToMock.symbol.typeWithParameters(mockedClass.typeParameters)
@@ -102,12 +101,10 @@ fun buildMockClass(
102101
}
103102

104103
context(scope: TransformerScope)
105-
fun buildManyMockClass(classesToMock: List<IrClass>): IrClass {
104+
fun buildManyMockClass(name: Name, classesToMock: List<IrClass>): IrClass {
106105
val manyMocksMarkerClass = referenced(MokkeryIr.Class.mockMany(classesToMock.size))
107106
val mokkeryInstanceClass = referenced(MokkeryIr.Class.MokkeryInstanceScope)
108-
val mockedClass = irFactory.buildClass {
109-
name = manyMocksMarkerClass.kotlinFqName.createUniqueManyMockName()
110-
}
107+
val mockedClass = irFactory.buildClass { this.name = name }
111108
mockedClass.addToCurrentFile()
112109
classesToMock.forEach(mockedClass::copyTypeParametersFrom)
113110
mockedClass.createThisReceiverParameter()
@@ -223,8 +220,7 @@ private fun IrClass.addMockClassConstructor(
223220
arguments[4] = irGet(thisReceiver!!)
224221
arguments[5] = irGet(parameters[1])
225222
arguments[6] = spyParam?.let(::irGet) ?: irNull()
226-
arguments[7] = buildDefaultsExtractorFactoryIfRequired(
227-
className = this@addMockClassConstructor.name,
223+
arguments[7] = findOrBuildDefaultsExtractorFactoryIfRequired(
228224
classesToIntercept = classesToIntercept,
229225
bodyBuilder = this@irBlockBody
230226
)
@@ -252,12 +248,3 @@ private fun IrConstructor.addSpyParameter(classesToIntercept: List<IrClass>): Ir
252248
val classToSpy = classesToIntercept.singleOrNull() ?: error("Spy is not supported for intercepting multiple types!")
253249
return addValueParameter("obj", classToSpy.symbol.typeWithParameters(parentAsClass.typeParameters))
254250
}
255-
256-
private fun Name.createUniqueMockName(type: String) = asString()
257-
.plus("_$type}_${randomString()}")
258-
.let(Name::identifier)
259-
260-
private fun FqName.createUniqueManyMockName() = shortName()
261-
.asString()
262-
.plus("_${randomString()}")
263-
.let(Name::identifier)

mokkery-plugin/src/main/kotlin/dev/mokkery/plugin/ir/transformer/mock/ReplaceWithMock.kt

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package dev.mokkery.plugin.ir.transformer.mock
22

3-
import dev.mokkery.plugin.Cache
4-
import dev.mokkery.plugin.caches
53
import dev.mokkery.plugin.context.configuration
64
import dev.mokkery.plugin.defaultMockMode
7-
import dev.mokkery.plugin.ir.Caches
85
import dev.mokkery.plugin.ir.IrMokkeryKind.Mock
96
import dev.mokkery.plugin.ir.IrMokkeryKind.Spy
107
import dev.mokkery.plugin.ir.MokkeryIr
@@ -19,6 +16,7 @@ import dev.mokkery.plugin.ir.kClassReference
1916
import dev.mokkery.plugin.ir.platform
2017
import dev.mokkery.plugin.ir.transformer.core.TransformerScope
2118
import dev.mokkery.plugin.ir.transformer.core.declarationIrBuilder
19+
import dev.mokkery.plugin.ir.transformer.core.findOrBuildClassInCurrentFile
2220
import dev.mokkery.plugin.ir.transformer.core.irGetMokkeryScopeGlobal
2321
import dev.mokkery.plugin.ir.transformer.core.referenced
2422
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
@@ -36,10 +34,10 @@ fun IrCall.replaceMockCall(): IrExpression {
3634
val typeToMock = typeArguments.firstOrNull() ?: return this
3735
val classToMock = typeToMock.getClass() ?: return this
3836
if (platform.isJs() && classToMock.defaultType.isAnyFunction()) return buildMockJsFunction(this, Mock)
39-
val mockedClass = cachedMockImplementationClass(
40-
typeToMock = classToMock,
41-
cacheKey = Caches.mockClasses,
42-
builder = { buildMockClass(Mock, it) }
37+
val mockedClass = findOrBuildClassInCurrentFile(
38+
nameBase = "Mock",
39+
nameHashSource = listOf(classToMock),
40+
builder = { buildMockClass(it, Mock, classToMock) }
4341
)
4442
return declarationIrBuilder {
4543
irMockConstructorCall(mockedClass, this@replaceMockCall)
@@ -48,12 +46,12 @@ fun IrCall.replaceMockCall(): IrExpression {
4846

4947
context(scope: TransformerScope)
5048
fun IrCall.replaceMockManyCall(): IrExpression {
51-
val classes = typeArguments.mapNotNullTo(mutableSetOf()) { it?.getClass() }
52-
if (classes.isEmpty()) return this
53-
val mockedClass = cachedMockImplementationClass(
54-
typeToMock = classes,
55-
cacheKey = Caches.mockManyClasses,
56-
builder = { buildManyMockClass(it.toList()) }
49+
val classesToMock = typeArguments.mapNotNull { it?.getClass() }
50+
if (classesToMock.isEmpty()) return this
51+
val mockedClass = findOrBuildClassInCurrentFile(
52+
nameBase = "MockMany${classesToMock.size}",
53+
nameHashSource = classesToMock,
54+
builder = { buildManyMockClass(it, classesToMock) }
5755
)
5856
return declarationIrBuilder {
5957
irMockConstructorCall(mockedClass, this@replaceMockManyCall)
@@ -63,12 +61,12 @@ fun IrCall.replaceMockManyCall(): IrExpression {
6361
context(scope: TransformerScope)
6462
fun IrCall.replaceSpyCall(): IrExpression {
6563
val typeToMock = typeArguments.firstOrNull() ?: return this
66-
val classToMock = typeToMock.getClass() ?: return this
67-
if (platform.isJs() && classToMock.defaultType.isAnyFunction()) return buildMockJsFunction(this, Spy)
68-
val spiedClass = cachedMockImplementationClass(
69-
typeToMock = classToMock,
70-
cacheKey = Caches.spyClasses,
71-
builder = { buildMockClass(Spy, it) }
64+
val classToSpy = typeToMock.getClass() ?: return this
65+
if (platform.isJs() && classToSpy.defaultType.isAnyFunction()) return buildMockJsFunction(this, Spy)
66+
val spiedClass = findOrBuildClassInCurrentFile(
67+
nameBase = "Spy",
68+
nameHashSource = listOf(classToSpy),
69+
builder = { buildMockClass(it, Spy, classToSpy) }
7270
)
7371
return declarationIrBuilder {
7472
irSpyConstructorCall(spiedClass, this@replaceSpyCall)
@@ -118,17 +116,3 @@ private fun IrBuilderWithScope.irSpyConstructorCall(
118116
arguments[4 + index] = kClassReference(it ?: anyType)
119117
}
120118
}
121-
122-
context(scope: TransformerScope)
123-
private fun <T> cachedMockImplementationClass(
124-
typeToMock: T,
125-
cacheKey: Cache.Key<T, IrClass>,
126-
builder: (T) -> IrClass
127-
): IrClass {
128-
val cache = caches[cacheKey]
129-
val cached = cache[typeToMock]
130-
if (cached != null) return cached
131-
val newClass = builder(typeToMock)
132-
cache[typeToMock] = newClass
133-
return newClass
134-
}

0 commit comments

Comments
 (0)