Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 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 | |
Louis Pullen-Freilich | 3ebca69 | 2020-07-24 01:10:07 +0100 | [diff] [blame^] | 17 | package androidx.compose.compiler.plugins.kotlin.lower |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 18 | |
Louis Pullen-Freilich | 3ebca69 | 2020-07-24 01:10:07 +0100 | [diff] [blame^] | 19 | import androidx.compose.compiler.plugins.kotlin.ComposeFqNames |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 20 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext |
| 21 | import org.jetbrains.kotlin.backend.common.ir.addChild |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 22 | import org.jetbrains.kotlin.backend.common.ir.copyTo |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 23 | import org.jetbrains.kotlin.backend.common.ir.createParameterDeclarations |
| 24 | import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder |
| 25 | import org.jetbrains.kotlin.descriptors.ClassKind |
| 26 | import org.jetbrains.kotlin.descriptors.Visibilities |
| 27 | import org.jetbrains.kotlin.ir.IrStatement |
| 28 | import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET |
| 29 | import org.jetbrains.kotlin.ir.builders.declarations.addConstructor |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 30 | import org.jetbrains.kotlin.ir.builders.declarations.addFunction |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 31 | import org.jetbrains.kotlin.ir.builders.declarations.addGetter |
| 32 | import org.jetbrains.kotlin.ir.builders.declarations.addProperty |
| 33 | import org.jetbrains.kotlin.ir.builders.declarations.addSetter |
| 34 | import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 35 | import org.jetbrains.kotlin.ir.builders.declarations.buildClass |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 36 | import org.jetbrains.kotlin.ir.builders.declarations.buildField |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 37 | import org.jetbrains.kotlin.ir.builders.irBlock |
| 38 | import org.jetbrains.kotlin.ir.builders.irBlockBody |
| 39 | import org.jetbrains.kotlin.ir.builders.irCall |
| 40 | import org.jetbrains.kotlin.ir.builders.irDelegatingConstructorCall |
| 41 | import org.jetbrains.kotlin.ir.builders.irGet |
| 42 | import org.jetbrains.kotlin.ir.builders.irGetField |
| 43 | import org.jetbrains.kotlin.ir.builders.irIfNull |
| 44 | import org.jetbrains.kotlin.ir.builders.irReturn |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 45 | import org.jetbrains.kotlin.ir.builders.irSet |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 46 | import org.jetbrains.kotlin.ir.builders.irSetField |
| 47 | import org.jetbrains.kotlin.ir.builders.irString |
| 48 | import org.jetbrains.kotlin.ir.builders.irTemporary |
| 49 | import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer |
| 50 | import org.jetbrains.kotlin.ir.declarations.IrClass |
| 51 | import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin |
| 52 | import org.jetbrains.kotlin.ir.declarations.IrEnumEntry |
| 53 | import org.jetbrains.kotlin.ir.declarations.IrFile |
| 54 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment |
| 55 | import org.jetbrains.kotlin.ir.declarations.IrProperty |
| 56 | import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction |
| 57 | import org.jetbrains.kotlin.ir.declarations.IrValueParameter |
| 58 | import org.jetbrains.kotlin.ir.declarations.IrVariable |
| 59 | import org.jetbrains.kotlin.ir.expressions.IrBlock |
| 60 | import org.jetbrains.kotlin.ir.expressions.IrBlockBody |
| 61 | import org.jetbrains.kotlin.ir.expressions.IrBody |
| 62 | import org.jetbrains.kotlin.ir.expressions.IrBranch |
| 63 | import org.jetbrains.kotlin.ir.expressions.IrCall |
| 64 | import org.jetbrains.kotlin.ir.expressions.IrComposite |
| 65 | import org.jetbrains.kotlin.ir.expressions.IrConst |
| 66 | import org.jetbrains.kotlin.ir.expressions.IrConstKind |
| 67 | import org.jetbrains.kotlin.ir.expressions.IrConstructorCall |
| 68 | import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall |
| 69 | import org.jetbrains.kotlin.ir.expressions.IrElseBranch |
Leland Richardson | 793de65 | 2020-06-30 22:47:32 -0700 | [diff] [blame] | 70 | import org.jetbrains.kotlin.ir.expressions.IrEnumConstructorCall |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 71 | import org.jetbrains.kotlin.ir.expressions.IrExpression |
| 72 | import org.jetbrains.kotlin.ir.expressions.IrLoop |
| 73 | import org.jetbrains.kotlin.ir.expressions.IrSetField |
| 74 | import org.jetbrains.kotlin.ir.expressions.IrSetVariable |
| 75 | import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin |
| 76 | import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation |
| 77 | import org.jetbrains.kotlin.ir.expressions.IrTry |
| 78 | import org.jetbrains.kotlin.ir.expressions.IrVararg |
| 79 | import org.jetbrains.kotlin.ir.expressions.IrVarargElement |
| 80 | import org.jetbrains.kotlin.ir.expressions.IrWhen |
| 81 | import org.jetbrains.kotlin.ir.expressions.impl.IrBranchImpl |
| 82 | import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl |
| 83 | import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl |
| 84 | import org.jetbrains.kotlin.ir.expressions.impl.IrElseBranchImpl |
Leland Richardson | 9669e6a | 2020-07-10 16:26:10 -0700 | [diff] [blame] | 85 | import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 86 | import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl |
| 87 | import org.jetbrains.kotlin.ir.expressions.impl.IrStringConcatenationImpl |
| 88 | import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl |
| 89 | import org.jetbrains.kotlin.ir.types.IrType |
| 90 | import org.jetbrains.kotlin.ir.types.defaultType |
| 91 | import org.jetbrains.kotlin.ir.types.makeNullable |
| 92 | import org.jetbrains.kotlin.ir.types.typeWith |
| 93 | import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper |
| 94 | import org.jetbrains.kotlin.ir.util.constructors |
| 95 | import org.jetbrains.kotlin.ir.util.defaultType |
| 96 | import org.jetbrains.kotlin.ir.util.getPropertyGetter |
| 97 | import org.jetbrains.kotlin.ir.util.isAnnotationClass |
| 98 | import org.jetbrains.kotlin.ir.util.parentAsClass |
| 99 | import org.jetbrains.kotlin.ir.util.primaryConstructor |
| 100 | import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid |
| 101 | import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils |
| 102 | import org.jetbrains.kotlin.name.Name |
| 103 | import org.jetbrains.kotlin.resolve.BindingTrace |
| 104 | |
| 105 | /** |
| 106 | * This transformer transforms constant literal expressions into expressions which read a |
| 107 | * MutableState instance so that changes to the source code of the constant literal can be |
| 108 | * communicated to the runtime without a recompile. This transformation is intended to improve |
| 109 | * developer experience and should never be enabled in a release build as it will significantly |
| 110 | * slow down performance-conscious code. |
| 111 | * |
| 112 | * The nontrivial piece of this transform is to create a stable "durable" unique key for every |
| 113 | * single constant in the module. It does this by creating a path-based key which roughly maps to |
| 114 | * the semantic structure of the code, and uses an incrementing index on sibling constants as a |
| 115 | * last resort. The constant expressions in the IR will be transformed into property getter calls |
| 116 | * to a synthetic "Live Literals" class that is generated per file. The class is an object |
| 117 | * singleton where each property is lazily backed by a MutableState instance which is accessed |
| 118 | * using the runtime's `liveLiteral(String,T)` top level API. |
| 119 | * |
| 120 | * Roughly speaking, the transform will turn the following: |
| 121 | * |
| 122 | * // file: Foo.kt |
| 123 | * fun Foo() { |
| 124 | * print("Hello World") |
| 125 | * } |
| 126 | * |
| 127 | * into the following equivalent representation: |
| 128 | * |
| 129 | * // file: Foo.kt |
| 130 | * fun Foo() { |
| 131 | * print(LiveLiterals$FooKt.`getString$arg-0$call-print$fun-Foo`()) |
| 132 | * } |
| 133 | * object LiveLiterals$FooKt { |
Leland Richardson | 9669e6a | 2020-07-10 16:26:10 -0700 | [diff] [blame] | 134 | * var `String$arg-0$call-print$fun-Foo`: String = "Hello World" |
| 135 | * var `State$String$arg-0$call-print$fun-Foo`: MutableState<String>? = null |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 136 | * fun `getString$arg-0$call-print$fun-Foo`(): String { |
| 137 | * val field = this.`String$arg-0$call-print$fun-Foo` |
| 138 | * val state = if (field == null) { |
Leland Richardson | 9669e6a | 2020-07-10 16:26:10 -0700 | [diff] [blame] | 139 | * val tmp = liveLiteral( |
| 140 | * "String$arg-0$call-print$fun-Foo", |
| 141 | * this.`String$arg-0$call-print$fun-Foo` |
| 142 | * ) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 143 | * this.`String$arg-0$call-print$fun-Foo` = tmp |
| 144 | * tmp |
| 145 | * } else field |
| 146 | * return field.value |
| 147 | * } |
| 148 | * } |
| 149 | * |
| 150 | * @see DurableKeyVisitor |
| 151 | */ |
| 152 | open class LiveLiteralTransformer( |
| 153 | private val liveLiteralsEnabled: Boolean, |
| 154 | private val keyVisitor: DurableKeyVisitor, |
| 155 | context: IrPluginContext, |
| 156 | symbolRemapper: DeepCopySymbolRemapper, |
| 157 | bindingTrace: BindingTrace |
| 158 | ) : |
| 159 | AbstractComposeLowering(context, symbolRemapper, bindingTrace), |
| 160 | ModuleLoweringPass { |
| 161 | |
| 162 | override fun lower(module: IrModuleFragment) { |
| 163 | module.transformChildrenVoid(this) |
| 164 | } |
| 165 | |
| 166 | private val liveLiteral = |
| 167 | getInternalFunction("liveLiteral").bindIfNecessary() |
| 168 | private val liveLiteralInfoAnnotation = |
| 169 | getInternalClass("LiveLiteralInfo").bindIfNecessary() |
| 170 | private val liveLiteralFileInfoAnnotation = |
| 171 | getInternalClass("LiveLiteralFileInfo").bindIfNecessary() |
| 172 | private val stateInterface = |
Leland Richardson | 3cc3c0a | 2020-07-23 11:50:55 -0700 | [diff] [blame] | 173 | getTopLevelClass(ComposeFqNames.fqNameFor("State")).bindIfNecessary() |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 174 | private val NoLiveLiteralsAnnotation = |
Leland Richardson | 3cc3c0a | 2020-07-23 11:50:55 -0700 | [diff] [blame] | 175 | getTopLevelClass(ComposeFqNames.fqNameFor("NoLiveLiterals")).bindIfNecessary() |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 176 | |
| 177 | private fun IrAnnotationContainer.hasNoLiveLiteralsAnnotation(): Boolean = annotations.any { |
| 178 | it.symbol.bindIfNecessary().owner == NoLiveLiteralsAnnotation.owner.primaryConstructor |
| 179 | } |
| 180 | |
| 181 | private fun <T> enter(key: String, block: () -> T) = keyVisitor.enter(key, block) |
| 182 | private fun <T> siblings(key: String, block: () -> T) = keyVisitor.siblings(key, block) |
| 183 | private fun <T> siblings(block: () -> T) = keyVisitor.siblings(block) |
| 184 | private var liveLiteralsClass: IrClass? = null |
| 185 | private var currentFile: IrFile? = null |
| 186 | |
| 187 | private fun irGetLiveLiteralsClass(): IrExpression { |
| 188 | return IrGetObjectValueImpl( |
| 189 | startOffset = UNDEFINED_OFFSET, |
| 190 | endOffset = UNDEFINED_OFFSET, |
| 191 | type = liveLiteralsClass!!.defaultType, |
| 192 | symbol = liveLiteralsClass!!.symbol |
| 193 | ) |
| 194 | } |
| 195 | |
| 196 | private fun Name.asJvmFriendlyString(): String { |
| 197 | return if (!isSpecial) identifier |
| 198 | else asString().replace('<', '$').replace('>', '$') |
| 199 | } |
| 200 | |
| 201 | private fun irLiveLiteralInfoAnnotation( |
| 202 | key: String, |
| 203 | offset: Int |
| 204 | ): IrConstructorCall = IrConstructorCallImpl( |
| 205 | UNDEFINED_OFFSET, |
| 206 | UNDEFINED_OFFSET, |
| 207 | liveLiteralInfoAnnotation.defaultType, |
| 208 | liveLiteralInfoAnnotation.constructors.single(), |
| 209 | 0, |
| 210 | 0, |
| 211 | 2 |
| 212 | ).apply { |
| 213 | putValueArgument(0, irConst(key)) |
| 214 | putValueArgument(1, irConst(offset)) |
| 215 | } |
| 216 | |
| 217 | private fun irLiveLiteralFileInfoAnnotation( |
| 218 | file: String |
| 219 | ): IrConstructorCall = IrConstructorCallImpl( |
| 220 | UNDEFINED_OFFSET, |
| 221 | UNDEFINED_OFFSET, |
| 222 | liveLiteralFileInfoAnnotation.defaultType, |
| 223 | liveLiteralFileInfoAnnotation.constructors.single(), |
| 224 | 0, |
| 225 | 0, |
| 226 | 1 |
| 227 | ).apply { |
| 228 | putValueArgument(0, irConst(file)) |
| 229 | } |
| 230 | |
| 231 | private fun irLiveLiteralGetter( |
| 232 | key: String, |
| 233 | literalValue: IrExpression, |
| 234 | literalType: IrType |
| 235 | ): IrSimpleFunction { |
| 236 | val clazz = liveLiteralsClass!! |
| 237 | val stateType = stateInterface.owner.typeWith(literalType).makeNullable() |
| 238 | val stateGetValue = stateInterface.getPropertyGetter("value")!! |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 239 | val defaultProp = clazz.addProperty { |
| 240 | name = Name.identifier(key) |
| 241 | visibility = Visibilities.PRIVATE |
| 242 | }.also { p -> |
| 243 | p.backingField = buildField { |
| 244 | name = Name.identifier(key) |
| 245 | isStatic = true |
| 246 | type = literalType |
| 247 | visibility = Visibilities.PRIVATE |
| 248 | }.also { f -> |
| 249 | f.correspondingPropertySymbol = p.symbol |
| 250 | f.parent = clazz |
| 251 | f.initializer = IrExpressionBodyImpl( |
| 252 | literalValue.startOffset, |
| 253 | literalValue.endOffset, |
| 254 | literalValue |
| 255 | ) |
| 256 | } |
| 257 | p.addGetter { |
| 258 | returnType = literalType |
| 259 | visibility = Visibilities.PRIVATE |
| 260 | origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR |
| 261 | }.also { fn -> |
| 262 | val thisParam = clazz.thisReceiver!!.copyTo(fn) |
| 263 | fn.dispatchReceiverParameter = thisParam |
| 264 | fn.body = DeclarationIrBuilder(context, fn.symbol).irBlockBody { |
| 265 | +irReturn(irGetField(irGet(thisParam), p.backingField!!)) |
| 266 | } |
| 267 | } |
Leland Richardson | 9669e6a | 2020-07-10 16:26:10 -0700 | [diff] [blame] | 268 | } |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 269 | val stateProp = clazz.addProperty { |
| 270 | name = Name.identifier("State\$$key") |
| 271 | visibility = Visibilities.PRIVATE |
| 272 | isVar = true |
| 273 | }.also { p -> |
| 274 | p.backingField = buildField { |
| 275 | name = Name.identifier("State\$$key") |
| 276 | type = stateType |
| 277 | visibility = Visibilities.PRIVATE |
| 278 | isStatic = true |
| 279 | }.also { f -> |
| 280 | f.correspondingPropertySymbol = p.symbol |
| 281 | f.parent = clazz |
| 282 | } |
| 283 | p.addGetter { |
| 284 | returnType = stateType |
| 285 | visibility = Visibilities.PRIVATE |
| 286 | origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR |
| 287 | }.also { fn -> |
| 288 | val thisParam = clazz.thisReceiver!!.copyTo(fn) |
| 289 | fn.dispatchReceiverParameter = thisParam |
| 290 | fn.body = DeclarationIrBuilder(context, fn.symbol).irBlockBody { |
| 291 | +irReturn(irGetField(irGet(thisParam), p.backingField!!)) |
| 292 | } |
| 293 | } |
| 294 | p.addSetter { |
| 295 | returnType = context.irBuiltIns.unitType |
| 296 | visibility = Visibilities.PRIVATE |
| 297 | origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR |
| 298 | }.also { fn -> |
| 299 | val thisParam = clazz.thisReceiver!!.copyTo(fn) |
| 300 | fn.dispatchReceiverParameter = thisParam |
| 301 | val valueParam = fn.addValueParameter("value", stateType) |
| 302 | fn.body = DeclarationIrBuilder(context, fn.symbol).irBlockBody { |
| 303 | +irSetField(irGet(thisParam), p.backingField!!, irGet(valueParam)) |
| 304 | } |
| 305 | } |
| 306 | } |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 307 | return clazz.addFunction( |
| 308 | name = key, |
| 309 | returnType = literalType |
| 310 | ).also { fn -> |
| 311 | val thisParam = fn.dispatchReceiverParameter!! |
Jim Sproch | a88c07a | 2020-06-25 13:00:03 -0700 | [diff] [blame] | 312 | fn.annotations += irLiveLiteralInfoAnnotation(key, literalValue.startOffset) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 313 | fn.body = DeclarationIrBuilder(context, fn.symbol).irBlockBody { |
Leland Richardson | 9669e6a | 2020-07-10 16:26:10 -0700 | [diff] [blame] | 314 | // val a = stateField |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 315 | // val b = if (a == null) { |
Leland Richardson | 9669e6a | 2020-07-10 16:26:10 -0700 | [diff] [blame] | 316 | // val c = liveLiteralState("key", defaultValueField) |
| 317 | // stateField = c |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 318 | // c |
| 319 | // } else a |
| 320 | // return b.value |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 321 | val a = irTemporary(irGet(stateType, irGet(thisParam), stateProp.getter!!.symbol)) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 322 | val b = irIfNull( |
| 323 | type = stateType, |
| 324 | subject = irGet(a), |
| 325 | thenPart = irBlock(resultType = stateType) { |
| 326 | val liveLiteralCall = irCall(liveLiteral).apply { |
| 327 | putValueArgument(0, irString(key)) |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 328 | putValueArgument(1, irGet( |
| 329 | literalType, |
| 330 | irGet(thisParam), |
| 331 | defaultProp.getter!!.symbol |
| 332 | )) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 333 | putTypeArgument(0, literalType) |
| 334 | } |
| 335 | val c = irTemporary(liveLiteralCall) |
Leland Richardson | 76b8acb | 2020-07-15 17:35:41 -0700 | [diff] [blame] | 336 | +irSet( |
| 337 | stateType, |
| 338 | irGet(thisParam), |
| 339 | stateProp.setter!!.symbol, |
| 340 | irGet(c) |
| 341 | ) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 342 | +irGet(c) |
| 343 | }, |
| 344 | elsePart = irGet(a) |
| 345 | ) |
Jim Sproch | a88c07a | 2020-06-25 13:00:03 -0700 | [diff] [blame] | 346 | val call = IrCallImpl( |
| 347 | UNDEFINED_OFFSET, |
| 348 | UNDEFINED_OFFSET, |
| 349 | literalType, |
| 350 | stateGetValue, |
| 351 | IrStatementOrigin.FOR_LOOP_ITERATOR |
| 352 | ).apply { |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 353 | dispatchReceiver = b |
Jim Sproch | a88c07a | 2020-06-25 13:00:03 -0700 | [diff] [blame] | 354 | } |
| 355 | |
| 356 | +irReturn(call) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 357 | } |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | override fun <T> visitConst(expression: IrConst<T>): IrExpression { |
| 362 | when (expression.kind) { |
| 363 | IrConstKind.Null -> return expression |
| 364 | } |
| 365 | val (key, success) = keyVisitor.buildPath( |
| 366 | prefix = expression.kind.asString, |
| 367 | pathSeparator = "\$", |
| 368 | siblingSeparator = "-" |
| 369 | ) |
| 370 | // NOTE: Even if `liveLiteralsEnabled` is false, we are still going to throw an exception |
| 371 | // here because the presence of a duplicate key represents a bug in this transform since |
| 372 | // it should be impossible. By checking this always, we are making it so that bugs in |
| 373 | // this transform will get caught _early_ and that there will be implicitly high coverage |
| 374 | // of the key generation algorithm despite this transform only being used by tooling. |
| 375 | // Developers have the ability to "silence" this exception by marking the surrounding |
| 376 | // class/file/function with the `@NoLiveLiterals` annotation. |
| 377 | if (!success) { |
| 378 | val file = currentFile ?: return expression |
| 379 | val src = file.fileEntry.getSourceRangeInfo( |
| 380 | expression.startOffset, |
| 381 | expression.endOffset |
| 382 | ) |
| 383 | |
| 384 | error( |
| 385 | "Duplicate live literal key found: $key\n" + |
| 386 | "Caused by element at: " + |
| 387 | "${src.filePath}:${src.startLineNumber}:${src.startColumnNumber}\n" + |
| 388 | "If you encounter this error, please file a bug at " + |
| 389 | "https://issuetracker.google.com/issues?q=componentid:610764\n" + |
| 390 | "Try adding the `@NoLiveLiterals` annotation around the surrounding code to " + |
| 391 | "avoid this exception." |
| 392 | ) |
| 393 | } |
| 394 | // If live literals are enabled, don't do anything |
| 395 | if (!liveLiteralsEnabled) return expression |
| 396 | |
| 397 | // create the getter function on the live literals class |
| 398 | val getter = irLiveLiteralGetter( |
| 399 | key = key, |
| 400 | literalValue = expression.copy(), |
| 401 | literalType = expression.type |
| 402 | ) |
| 403 | |
| 404 | // return a call to the getter in place of the constant |
| 405 | return IrCallImpl( |
| 406 | expression.startOffset, |
| 407 | expression.endOffset, |
| 408 | expression.type, |
| 409 | getter.symbol |
| 410 | ).apply { |
| 411 | dispatchReceiver = irGetLiveLiteralsClass() |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | override fun visitClass(declaration: IrClass): IrStatement { |
| 416 | if (declaration.hasNoLiveLiteralsAnnotation()) return declaration |
| 417 | // constants in annotations need to be compile-time values, so we can never transform them |
| 418 | if (declaration.isAnnotationClass) return declaration |
| 419 | return siblings("class-${declaration.name.asJvmFriendlyString()}") { |
| 420 | super.visitClass(declaration) |
| 421 | } |
| 422 | } |
| 423 | |
| 424 | open fun makeKeySet(): MutableSet<String> { |
| 425 | return mutableSetOf() |
| 426 | } |
| 427 | |
| 428 | override fun visitFile(declaration: IrFile): IrFile { |
| 429 | if (declaration.hasNoLiveLiteralsAnnotation()) return declaration |
| 430 | val filePath = declaration.fileEntry.name |
| 431 | val fileName = filePath.split('/').last() |
| 432 | val keys = makeKeySet() |
| 433 | return keyVisitor.root(keys) { |
| 434 | val prevClass = liveLiteralsClass |
| 435 | val nextClass = buildClass { |
| 436 | kind = ClassKind.OBJECT |
| 437 | visibility = Visibilities.INTERNAL |
| 438 | val shortName = PackagePartClassUtils.getFilePartShortName(fileName) |
| 439 | // the name of the LiveLiterals class is per-file, so we use the same name that |
| 440 | // the kotlin file class lowering produces, prefixed with `LiveLiterals$`. |
| 441 | name = Name.identifier("LiveLiterals${"$"}$shortName") |
| 442 | }.also { |
| 443 | it.createParameterDeclarations() |
| 444 | |
| 445 | // store the full file path to the file that this class is associated with in an |
| 446 | // annotation on the class. This will be used by tooling to associate the keys |
| 447 | // inside of this class with actual PSI in the editor. |
Jim Sproch | a88c07a | 2020-06-25 13:00:03 -0700 | [diff] [blame] | 448 | it.annotations += irLiveLiteralFileInfoAnnotation(declaration.fileEntry.name) |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 449 | it.addConstructor { |
| 450 | isPrimary = true |
| 451 | }.also { ctor -> |
| 452 | ctor.body = DeclarationIrBuilder(context, it.symbol).irBlockBody { |
| 453 | +irDelegatingConstructorCall( |
| 454 | context |
| 455 | .irBuiltIns |
| 456 | .anyClass |
| 457 | .bindIfNecessary() |
| 458 | .owner |
| 459 | .primaryConstructor!! |
| 460 | ) |
| 461 | } |
| 462 | } |
| 463 | } |
| 464 | try { |
| 465 | liveLiteralsClass = nextClass |
| 466 | currentFile = declaration |
| 467 | val file = super.visitFile(declaration) |
| 468 | // if there were no constants found in the entire file, then we don't need to |
| 469 | // create this class at all |
| 470 | if (liveLiteralsEnabled && keys.isNotEmpty()) { |
| 471 | file.addChild(nextClass) |
| 472 | } |
| 473 | file |
| 474 | } finally { |
| 475 | liveLiteralsClass = prevClass |
| 476 | } |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | override fun visitTry(aTry: IrTry): IrExpression { |
| 481 | aTry.tryResult = enter("try") { |
| 482 | aTry.tryResult.transform(this, null) |
| 483 | } |
| 484 | siblings { |
| 485 | aTry.catches.forEach { |
| 486 | it.result = enter("catch") { it.result.transform(this, null) } |
| 487 | } |
| 488 | } |
| 489 | aTry.finallyExpression = enter("finally") { |
| 490 | aTry.finallyExpression?.transform(this, null) |
| 491 | } |
| 492 | return aTry |
| 493 | } |
| 494 | |
| 495 | override fun visitDelegatingConstructorCall( |
| 496 | expression: IrDelegatingConstructorCall |
| 497 | ): IrExpression { |
| 498 | val owner = expression.symbol.bindIfNecessary().owner |
| 499 | |
| 500 | // annotations are represented as constructor calls in IR, but the parameters need to be |
| 501 | // compile-time values only, so we can't transform them at all. |
| 502 | if (owner.parentAsClass.isAnnotationClass) return expression |
| 503 | |
| 504 | val name = owner.name.asJvmFriendlyString() |
| 505 | |
| 506 | return enter("call-$name") { |
| 507 | expression.dispatchReceiver = enter("\$this") { |
| 508 | expression.dispatchReceiver?.transform(this, null) |
| 509 | } |
| 510 | expression.extensionReceiver = enter("\$\$this") { |
| 511 | expression.extensionReceiver?.transform(this, null) |
| 512 | } |
| 513 | |
| 514 | for (i in 0 until expression.valueArgumentsCount) { |
| 515 | val arg = expression.getValueArgument(i) |
| 516 | if (arg != null) { |
| 517 | enter("arg-$i") { |
| 518 | expression.putValueArgument(i, arg.transform(this, null)) |
| 519 | } |
| 520 | } |
| 521 | } |
| 522 | expression |
| 523 | } |
| 524 | } |
| 525 | |
Leland Richardson | 793de65 | 2020-06-30 22:47:32 -0700 | [diff] [blame] | 526 | override fun visitEnumConstructorCall(expression: IrEnumConstructorCall): IrExpression { |
| 527 | val owner = expression.symbol.bindIfNecessary().owner |
| 528 | val name = owner.name.asJvmFriendlyString() |
| 529 | |
| 530 | return enter("call-$name") { |
| 531 | expression.dispatchReceiver = enter("\$this") { |
| 532 | expression.dispatchReceiver?.transform(this, null) |
| 533 | } |
| 534 | expression.extensionReceiver = enter("\$\$this") { |
| 535 | expression.extensionReceiver?.transform(this, null) |
| 536 | } |
| 537 | |
| 538 | for (i in 0 until expression.valueArgumentsCount) { |
| 539 | val arg = expression.getValueArgument(i) |
| 540 | if (arg != null) { |
| 541 | enter("arg-$i") { |
| 542 | expression.putValueArgument(i, arg.transform(this, null)) |
| 543 | } |
| 544 | } |
| 545 | } |
| 546 | expression |
| 547 | } |
| 548 | } |
| 549 | |
Leland Richardson | e49cef8f | 2020-06-27 11:33:36 -0700 | [diff] [blame] | 550 | override fun visitConstructorCall(expression: IrConstructorCall): IrExpression { |
| 551 | val owner = expression.symbol.bindIfNecessary().owner |
| 552 | |
| 553 | // annotations are represented as constructor calls in IR, but the parameters need to be |
| 554 | // compile-time values only, so we can't transform them at all. |
| 555 | if (owner.parentAsClass.isAnnotationClass) return expression |
| 556 | |
| 557 | val name = owner.name.asJvmFriendlyString() |
| 558 | |
| 559 | return enter("call-$name") { |
| 560 | expression.dispatchReceiver = enter("\$this") { |
| 561 | expression.dispatchReceiver?.transform(this, null) |
| 562 | } |
| 563 | expression.extensionReceiver = enter("\$\$this") { |
| 564 | expression.extensionReceiver?.transform(this, null) |
| 565 | } |
| 566 | |
| 567 | for (i in 0 until expression.valueArgumentsCount) { |
| 568 | val arg = expression.getValueArgument(i) |
| 569 | if (arg != null) { |
| 570 | enter("arg-$i") { |
| 571 | expression.putValueArgument(i, arg.transform(this, null)) |
| 572 | } |
| 573 | } |
| 574 | } |
| 575 | expression |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | override fun visitCall(expression: IrCall): IrExpression { |
| 580 | val owner = expression.symbol.bindIfNecessary().owner |
| 581 | val name = owner.name.asJvmFriendlyString() |
| 582 | |
| 583 | return enter("call-$name") { |
| 584 | expression.dispatchReceiver = enter("\$this") { |
| 585 | expression.dispatchReceiver?.transform(this, null) |
| 586 | } |
| 587 | expression.extensionReceiver = enter("\$\$this") { |
| 588 | expression.extensionReceiver?.transform(this, null) |
| 589 | } |
| 590 | |
| 591 | for (i in 0 until expression.valueArgumentsCount) { |
| 592 | val arg = expression.getValueArgument(i) |
| 593 | if (arg != null) { |
| 594 | enter("arg-$i") { |
| 595 | expression.putValueArgument(i, arg.transform(this, null)) |
| 596 | } |
| 597 | } |
| 598 | } |
| 599 | expression |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | override fun visitEnumEntry(declaration: IrEnumEntry): IrStatement { |
| 604 | return enter("entry-${declaration.name.asJvmFriendlyString()}") { |
| 605 | super.visitEnumEntry(declaration) |
| 606 | } |
| 607 | } |
| 608 | |
| 609 | override fun visitVararg(expression: IrVararg): IrExpression { |
| 610 | if (expression !is IrVarargImpl) return expression |
| 611 | return enter("vararg") { |
| 612 | expression.elements.forEachIndexed { i, arg -> |
| 613 | expression.elements[i] = enter("$i") { |
| 614 | arg.transform(this, null) as IrVarargElement |
| 615 | } |
| 616 | } |
| 617 | expression |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement { |
| 622 | if (declaration.hasNoLiveLiteralsAnnotation()) return declaration |
| 623 | val name = declaration.name.asJvmFriendlyString() |
| 624 | val path = if (name == "<anonymous>") "lambda" else "fun-$name" |
| 625 | return enter(path) { super.visitSimpleFunction(declaration) } |
| 626 | } |
| 627 | |
| 628 | override fun visitLoop(loop: IrLoop): IrExpression { |
| 629 | return when (loop.origin) { |
| 630 | // in these cases, the compiler relies on a certain structure for the condition |
| 631 | // expression, so we only touch the body |
| 632 | IrStatementOrigin.WHILE_LOOP, |
| 633 | IrStatementOrigin.FOR_LOOP_INNER_WHILE -> enter("loop") { |
| 634 | loop.body = enter("body") { loop.body?.transform(this, null) } |
| 635 | loop |
| 636 | } |
| 637 | else -> enter("loop") { |
| 638 | loop.condition = enter("cond") { loop.condition.transform(this, null) } |
| 639 | loop.body = enter("body") { loop.body?.transform(this, null) } |
| 640 | loop |
| 641 | } |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression { |
| 646 | if (expression !is IrStringConcatenationImpl) return expression |
| 647 | return enter("str") { |
| 648 | siblings { |
| 649 | expression.arguments.forEachIndexed { index, expr -> |
| 650 | expression.arguments[index] = enter("$index") { |
| 651 | expr.transform(this, null) |
| 652 | } |
| 653 | } |
| 654 | expression |
| 655 | } |
| 656 | } |
| 657 | } |
| 658 | |
| 659 | override fun visitWhen(expression: IrWhen): IrExpression { |
| 660 | return when (expression.origin) { |
| 661 | // ANDAND needs to have an 'if true then false' body on its second branch, so only |
| 662 | // transform the first branch |
| 663 | IrStatementOrigin.ANDAND -> { |
| 664 | expression.branches[0] = expression.branches[0].transform(this, null) |
| 665 | expression |
| 666 | } |
| 667 | |
| 668 | // OROR condition should have an 'if a then true' body on its first branch, so only |
| 669 | // transform the second branch |
| 670 | IrStatementOrigin.OROR -> { |
| 671 | expression.branches[1] = expression.branches[1].transform(this, null) |
| 672 | expression |
| 673 | } |
| 674 | |
| 675 | IrStatementOrigin.IF -> siblings("if") { |
| 676 | super.visitWhen(expression) |
| 677 | } |
| 678 | |
| 679 | else -> siblings("when") { |
| 680 | super.visitWhen(expression) |
| 681 | } |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | override fun visitValueParameter(declaration: IrValueParameter): IrStatement { |
| 686 | return enter("param-${declaration.name.asJvmFriendlyString()}") { |
| 687 | super.visitValueParameter(declaration) |
| 688 | } |
| 689 | } |
| 690 | |
| 691 | override fun visitElseBranch(branch: IrElseBranch): IrElseBranch { |
| 692 | return IrElseBranchImpl( |
| 693 | startOffset = branch.startOffset, |
| 694 | endOffset = branch.endOffset, |
| 695 | // the condition of an else branch is a constant boolean but we don't want |
| 696 | // to convert it into a live literal, so we don't transform it |
| 697 | condition = branch.condition, |
| 698 | result = enter("else") { |
| 699 | branch.result.transform(this, null) |
| 700 | } |
| 701 | ) |
| 702 | } |
| 703 | |
| 704 | override fun visitBranch(branch: IrBranch): IrBranch { |
| 705 | return IrBranchImpl( |
| 706 | startOffset = branch.startOffset, |
| 707 | endOffset = branch.endOffset, |
| 708 | condition = enter("cond") { |
| 709 | branch.condition.transform(this, null) |
| 710 | }, |
| 711 | // only translate the result, as the branch is a constant boolean but we don't want |
| 712 | // to convert it into a live literal |
| 713 | result = enter("branch") { |
| 714 | branch.result.transform(this, null) |
| 715 | } |
| 716 | ) |
| 717 | } |
| 718 | |
| 719 | override fun visitComposite(expression: IrComposite): IrExpression { |
| 720 | return siblings { |
| 721 | super.visitComposite(expression) |
| 722 | } |
| 723 | } |
| 724 | |
| 725 | override fun visitBlock(expression: IrBlock): IrExpression { |
| 726 | return when (expression.origin) { |
| 727 | // The compiler relies on a certain structure for the "iterator" instantiation in For |
| 728 | // loops, so we avoid transforming the first statement in this case |
| 729 | IrStatementOrigin.FOR_LOOP, |
| 730 | IrStatementOrigin.FOR_LOOP_INNER_WHILE -> { |
| 731 | expression.statements[1] = expression.statements[1].transform(this, null) |
| 732 | expression |
| 733 | } |
| 734 | // IrStatementOrigin.SAFE_CALL |
| 735 | // IrStatementOrigin.WHEN |
| 736 | // IrStatementOrigin.IF |
| 737 | // IrStatementOrigin.ELVIS |
| 738 | // IrStatementOrigin.ARGUMENTS_REORDERING_FOR_CALL |
| 739 | else -> siblings { |
| 740 | super.visitBlock(expression) |
| 741 | } |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | override fun visitSetVariable(expression: IrSetVariable): IrExpression { |
| 746 | val owner = expression.symbol.bindIfNecessary().owner |
| 747 | val name = owner.name |
| 748 | return when (owner.origin) { |
| 749 | // for these synthetic variable declarations we want to avoid transforming them since |
| 750 | // the compiler will rely on their compile time value in some cases. |
| 751 | IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE -> expression |
| 752 | IrDeclarationOrigin.IR_TEMPORARY_VARIABLE -> expression |
| 753 | IrDeclarationOrigin.FOR_LOOP_VARIABLE -> expression |
| 754 | else -> enter("set-$name") { super.visitSetVariable(expression) } |
| 755 | } |
| 756 | } |
| 757 | |
| 758 | override fun visitSetField(expression: IrSetField): IrExpression { |
| 759 | val name = expression.symbol.bindIfNecessary().owner.name |
| 760 | return enter("set-$name") { super.visitSetField(expression) } |
| 761 | } |
| 762 | |
| 763 | override fun visitBlockBody(body: IrBlockBody): IrBody { |
| 764 | return siblings { |
| 765 | super.visitBlockBody(body) |
| 766 | } |
| 767 | } |
| 768 | |
| 769 | override fun visitVariable(declaration: IrVariable): IrStatement { |
| 770 | return enter("val-${declaration.name.asJvmFriendlyString()}") { |
| 771 | super.visitVariable(declaration) |
| 772 | } |
| 773 | } |
| 774 | |
| 775 | override fun visitProperty(declaration: IrProperty): IrStatement { |
| 776 | if (declaration.hasNoLiveLiteralsAnnotation()) return declaration |
| 777 | val backingField = declaration.backingField |
| 778 | val getter = declaration.getter |
| 779 | val setter = declaration.setter |
| 780 | val name = declaration.name.asJvmFriendlyString() |
| 781 | |
| 782 | return enter("val-$name") { |
| 783 | // turn them into live literals. We should consider transforming some simple cases like |
| 784 | // `val foo = 123`, but in general turning this initializer into a getter is not a |
| 785 | // safe operation. We should figure out a way to do this for "static" expressions |
| 786 | // though such as `val foo = 16.dp`. |
| 787 | declaration.backingField = backingField |
| 788 | declaration.getter = enter("get") { |
| 789 | getter?.transform(this, null) as? IrSimpleFunction |
| 790 | } |
| 791 | declaration.setter = enter("set") { |
| 792 | setter?.transform(this, null) as? IrSimpleFunction |
| 793 | } |
| 794 | declaration |
| 795 | } |
| 796 | } |
| 797 | } |