001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.codegen.inline;
018    
019    import com.intellij.util.ArrayUtil;
020    import kotlin.jvm.functions.Function0;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.kotlin.codegen.AsmUtil;
023    import org.jetbrains.kotlin.codegen.ClassBuilder;
024    import org.jetbrains.kotlin.codegen.FieldInfo;
025    import org.jetbrains.kotlin.codegen.StackValue;
026    import org.jetbrains.kotlin.codegen.state.GenerationState;
027    import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
028    import org.jetbrains.org.objectweb.asm.*;
029    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
030    import org.jetbrains.org.objectweb.asm.tree.*;
031    
032    import java.util.*;
033    
034    import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.isThis0;
035    import static org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.NO_ORIGIN;
036    
037    public class AnonymousObjectTransformer {
038    
039        protected final GenerationState state;
040    
041        protected final JetTypeMapper typeMapper;
042    
043        private final InlineResult transformationResult;
044    
045        private MethodNode constructor;
046    
047        private String sourceInfo;
048    
049        private String debugInfo;
050    
051        private SourceMapper sourceMapper;
052    
053        private final InliningContext inliningContext;
054    
055        private final Type oldObjectType;
056    
057        private final Type newLambdaType;
058    
059        private final ClassReader reader;
060    
061        private final boolean isSameModule;
062    
063        private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>();
064    
065        public AnonymousObjectTransformer(
066                @NotNull String objectInternalName,
067                @NotNull InliningContext inliningContext,
068                boolean isSameModule,
069                @NotNull Type newLambdaType
070        ) {
071            this.isSameModule = isSameModule;
072            this.state = inliningContext.state;
073            this.typeMapper = state.getTypeMapper();
074            this.inliningContext = inliningContext;
075            this.oldObjectType = Type.getObjectType(objectInternalName);
076            this.newLambdaType = newLambdaType;
077    
078            reader = InlineCodegenUtil.buildClassReaderByInternalName(state, objectInternalName);
079            transformationResult = InlineResult.create();
080        }
081    
082        @NotNull
083        public InlineResult doTransform(@NotNull AnonymousObjectGeneration anonymousObjectGen, @NotNull FieldRemapper parentRemapper) {
084            final List<InnerClassNode> innerClassNodes = new ArrayList<InnerClassNode>();
085            ClassBuilder classBuilder = createClassBuilder();
086            final List<MethodNode> methodsToTransform = new ArrayList<MethodNode>();
087    
088            reader.accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) {
089                @Override
090                public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
091                    if (signature != null) {
092                        ReifiedTypeInliner.SignatureReificationResult signatureResult = inliningContext.reifedTypeInliner.reifySignature(signature);
093                        signature = signatureResult.getNewSignature();
094                        transformationResult.getReifiedTypeParametersUsages().mergeAll(signatureResult.getTypeParametersUsages());
095                    }
096                    super.visit(version, access, name, signature, superName, interfaces);
097                }
098    
099                @Override
100                public void visitInnerClass(String name, String outerName, String innerName, int access) {
101                    innerClassNodes.add(new InnerClassNode(name, outerName, innerName, access));
102                }
103    
104                @Override
105                public void visitOuterClass(@NotNull String owner, String name, String desc) {
106                    InliningContext parent = inliningContext.getParent();
107                    assert parent != null : "Context for transformer should have parent one: " + inliningContext;
108    
109                    //we don't write owner info for lamdbas and SAMs just only for objects
110                    if (parent.isRoot() || parent.isInliningLambdaRootContext()) {
111                        //TODO: think about writing method info - there is some problem with new constructor desc calculation
112                        super.visitOuterClass(inliningContext.getParent().getClassNameToInline(), null, null);
113                        return;
114                    }
115    
116                    super.visitOuterClass(owner, name, desc);
117                }
118    
119                @Override
120                public MethodVisitor visitMethod(
121                        int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions
122                ) {
123                    MethodNode node = new MethodNode(access, name, desc, signature, exceptions);
124                    if (name.equals("<init>")){
125                        if (constructor != null)
126                            throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor");
127    
128                        constructor = node;
129                    } else {
130                        methodsToTransform.add(node);
131                    }
132                    return node;
133                }
134    
135                @Override
136                public FieldVisitor visitField(
137                        int access, @NotNull String name, @NotNull String desc, String signature, Object value
138                ) {
139                    addUniqueField(name);
140                    if (InlineCodegenUtil.isCapturedFieldName(name)) {
141                        return null;
142                    } else {
143                        return super.visitField(access, name, desc, signature, value);
144                    }
145                }
146    
147                @Override
148                public void visitSource(String source, String debug) {
149                    sourceInfo = source;
150                    debugInfo = debug;
151                }
152    
153                @Override
154                public void visitEnd() {
155    
156                }
157            }, ClassReader.SKIP_FRAMES);
158    
159            if (!inliningContext.isInliningLambda) {
160                if (debugInfo != null && !debugInfo.isEmpty()) {
161                    sourceMapper = SourceMapper.Companion.createFromSmap(SMAPParser.parse(debugInfo));
162                }
163                else {
164                    //seems we can't do any clever mapping cause we don't know any about original class name
165                    sourceMapper = IdenticalSourceMapper.INSTANCE$;
166                }
167                if (sourceInfo != null && !InlineCodegenUtil.GENERATE_SMAP) {
168                    classBuilder.visitSource(sourceInfo, debugInfo);
169                }
170            }
171            else {
172                if (sourceInfo != null) {
173                    classBuilder.visitSource(sourceInfo, debugInfo);
174                }
175                sourceMapper = IdenticalSourceMapper.INSTANCE$;
176            }
177    
178            ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
179            ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
180            List<CapturedParamInfo> additionalFakeParams =
181                    extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder,
182                                                                anonymousObjectGen, parentRemapper);
183            List<MethodVisitor> deferringMethods = new ArrayList();
184    
185            for (MethodNode next : methodsToTransform) {
186                MethodVisitor deferringVisitor = newMethod(classBuilder, next);
187                InlineResult funResult =
188                        inlineMethodAndUpdateGlobalResult(anonymousObjectGen, parentRemapper, deferringVisitor, next, allCapturedParamBuilder, false);
189    
190                Type returnType = Type.getReturnType(next.desc);
191                if (!AsmUtil.isPrimitive(returnType)) {
192                    String oldFunReturnType = returnType.getInternalName();
193                    String newFunReturnType = funResult.getChangedTypes().get(oldFunReturnType);
194                    if (newFunReturnType != null) {
195                        inliningContext.typeRemapper.addAdditionalMappings(oldFunReturnType, newFunReturnType);
196                    }
197                }
198                deferringMethods.add(deferringVisitor);
199            }
200    
201            for (MethodVisitor method : deferringMethods) {
202                method.visitEnd();
203            }
204    
205            generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, anonymousObjectGen, parentRemapper, additionalFakeParams);
206    
207            SourceMapper.Companion.flushToClassBuilder(sourceMapper, classBuilder);
208    
209            for (InnerClassNode node : innerClassNodes) {
210                classBuilder.getVisitor().visitInnerClass(node.name, node.outerName, node.innerName, node.access);
211            }
212    
213            classBuilder.done();
214    
215            anonymousObjectGen.setNewLambdaType(newLambdaType);
216            return transformationResult;
217        }
218    
219        @NotNull
220        private InlineResult inlineMethodAndUpdateGlobalResult(
221                @NotNull AnonymousObjectGeneration anonymousObjectGen,
222                @NotNull FieldRemapper parentRemapper,
223                @NotNull MethodVisitor deferringVisitor,
224                @NotNull MethodNode next,
225                @NotNull ParametersBuilder allCapturedParamBuilder,
226                boolean isConstructor
227        ) {
228            InlineResult funResult = inlineMethod(anonymousObjectGen, parentRemapper, deferringVisitor, next, allCapturedParamBuilder, isConstructor);
229            transformationResult.addAllClassesToRemove(funResult);
230            transformationResult.getReifiedTypeParametersUsages().mergeAll(funResult.getReifiedTypeParametersUsages());
231            return funResult;
232        }
233    
234        @NotNull
235        private InlineResult inlineMethod(
236                @NotNull AnonymousObjectGeneration anonymousObjectGen,
237                @NotNull FieldRemapper parentRemapper,
238                @NotNull MethodVisitor deferringVisitor,
239                @NotNull MethodNode sourceNode,
240                @NotNull ParametersBuilder capturedBuilder,
241                boolean isConstructor
242        ) {
243            ReifiedTypeParametersUsages typeParametersToReify = inliningContext.reifedTypeInliner.reifyInstructions(sourceNode);
244            Parameters parameters = isConstructor ? capturedBuilder.buildParameters() : getMethodParametersWithCaptured(capturedBuilder, sourceNode);
245    
246            RegeneratedLambdaFieldRemapper remapper =
247                    new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(),
248                                                       parameters, anonymousObjectGen.getCapturedLambdasToInline(),
249                                                       parentRemapper, isConstructor);
250    
251            MethodInliner inliner = new MethodInliner(sourceNode, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
252                                                      remapper, isSameModule, "Transformer for " + anonymousObjectGen.getOwnerInternalName(),
253                                                      sourceMapper);
254    
255            InlineResult result = inliner.doInline(deferringVisitor, new LocalVarRemapper(parameters, 0), false, LabelOwner.NOT_APPLICABLE);
256            result.getReifiedTypeParametersUsages().mergeAll(typeParametersToReify);
257            deferringVisitor.visitMaxs(-1, -1);
258            return result;
259        }
260    
261        private void generateConstructorAndFields(
262                @NotNull ClassBuilder classBuilder,
263                @NotNull ParametersBuilder allCapturedBuilder,
264                @NotNull ParametersBuilder constructorInlineBuilder,
265                @NotNull AnonymousObjectGeneration anonymousObjectGen,
266                @NotNull FieldRemapper parentRemapper,
267                @NotNull List<CapturedParamInfo> constructorAdditionalFakeParams
268        ) {
269            List<Type> descTypes = new ArrayList<Type>();
270    
271            Parameters constructorParams = constructorInlineBuilder.buildParameters();
272            int [] capturedIndexes = new int [constructorParams.getReal().size() + constructorParams.getCaptured().size()];
273            int index = 0;
274            int size = 0;
275    
276            //complex processing cause it could have super constructor call params
277            for (ParameterInfo info : constructorParams) {
278                if (!info.isSkipped()) { //not inlined
279                    if (info.isCaptured() || info instanceof CapturedParamInfo) {
280                        capturedIndexes[index] = size;
281                        index++;
282                    }
283    
284                    if (size != 0) { //skip this
285                        descTypes.add(info.getType());
286                    }
287                    size += info.getType().getSize();
288                }
289            }
290    
291            String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, descTypes.toArray(new Type[descTypes.size()]));
292            MethodVisitor constructorVisitor = classBuilder.newMethod(NO_ORIGIN,
293                                                                      AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
294                                                                      "<init>", constructorDescriptor,
295                                                                      null, ArrayUtil.EMPTY_STRING_ARRAY);
296    
297            //initialize captured fields
298            List<NewJavaField> newFieldsWithSkipped = TransformationUtilsKt.getNewFieldsToGenerate(allCapturedBuilder.listCaptured());
299            List<FieldInfo> fieldInfoWithSkipped = TransformationUtilsKt.transformToFieldInfo(newLambdaType, newFieldsWithSkipped);
300    
301            int paramIndex = 0;
302            InstructionAdapter capturedFieldInitializer = new InstructionAdapter(constructorVisitor);
303            for (int i = 0; i < fieldInfoWithSkipped.size(); i++) {
304                FieldInfo fieldInfo = fieldInfoWithSkipped.get(i);
305                if (!newFieldsWithSkipped.get(i).getSkip()) {
306                    AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, capturedIndexes[paramIndex], capturedFieldInitializer);
307                }
308                paramIndex++;
309            }
310    
311            //then transform constructor
312            //HACK: in inlinining into constructor we access original captured fields with field access not local var
313            //but this fields added to general params (this assumes local var access) not captured one,
314            //so we need to add them to captured params
315            for (CapturedParamInfo info : constructorAdditionalFakeParams) {
316                CapturedParamInfo fake = constructorInlineBuilder.addCapturedParamCopy(info);
317    
318                if (fake.getLambda() != null) {
319                    //set remap value to skip this fake (captured with lambda already skipped)
320                    StackValue composed = StackValue.field(fake.getType(),
321                                                           oldObjectType,
322                                                           fake.getNewFieldName(),
323                                                           false,
324                                                           StackValue.LOCAL_0);
325                    fake.setRemapValue(composed);
326                }
327            }
328    
329            inlineMethodAndUpdateGlobalResult(anonymousObjectGen, parentRemapper, capturedFieldInitializer, constructor, constructorInlineBuilder, true);
330            constructorVisitor.visitEnd();
331            AsmUtil.genClosureFields(TransformationUtilsKt.toNameTypePair(TransformationUtilsKt.filterSkipped(newFieldsWithSkipped)), classBuilder);
332            //TODO for inline method make public class
333            anonymousObjectGen.setNewConstructorDescriptor(constructorDescriptor);
334        }
335    
336        @NotNull
337        private Parameters getMethodParametersWithCaptured(
338                @NotNull ParametersBuilder capturedBuilder,
339                @NotNull MethodNode sourceNode
340        ) {
341            ParametersBuilder builder = ParametersBuilder.initializeBuilderFrom(oldObjectType, sourceNode.desc);
342            for (CapturedParamInfo param : capturedBuilder.listCaptured()) {
343                builder.addCapturedParamCopy(param);
344            }
345            return builder.buildParameters();
346        }
347    
348        @NotNull
349        private ClassBuilder createClassBuilder() {
350            ClassBuilder classBuilder = state.getFactory().newVisitor(NO_ORIGIN, newLambdaType, inliningContext.getRoot().callElement.getContainingFile());
351            return new RemappingClassBuilder(classBuilder, inliningContext.typeRemapper);
352        }
353    
354        @NotNull
355        private static DeferredMethodVisitor newMethod(@NotNull final ClassBuilder builder, @NotNull final MethodNode original) {
356            return new DeferredMethodVisitor(
357                    new MethodNode(original.access,
358                                   original.name,
359                                   original.desc,
360                                   original.signature,
361                                   ArrayUtil.toStringArray(original.exceptions)),
362    
363                    new Function0<MethodVisitor>() {
364                        @Override
365                        public MethodVisitor invoke() {
366                            return builder.newMethod(
367                                    NO_ORIGIN,
368                                    original.access,
369                                    original.name,
370                                    original.desc,
371                                    original.signature,
372                                    ArrayUtil.toStringArray(original.exceptions));
373                        }
374                    });
375        }
376    
377        private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
378                @NotNull MethodNode constructor,
379                @NotNull ParametersBuilder capturedParamBuilder,
380                @NotNull ParametersBuilder constructorParamBuilder,
381                @NotNull final AnonymousObjectGeneration anonymousObjectGen,
382                @NotNull FieldRemapper parentFieldRemapper
383        ) {
384    
385            CapturedParamOwner owner = new CapturedParamOwner() {
386                @Override
387                public Type getType() {
388                    return Type.getObjectType(anonymousObjectGen.getOwnerInternalName());
389                }
390            };
391    
392            Set<LambdaInfo> capturedLambdas = new LinkedHashSet<LambdaInfo>(); //captured var of inlined parameter
393            List<CapturedParamInfo> constructorAdditionalFakeParams = new ArrayList<CapturedParamInfo>();
394            Map<Integer, LambdaInfo> indexToLambda = anonymousObjectGen.getLambdasToInline();
395            Set<Integer> capturedParams = new HashSet<Integer>();
396    
397            //load captured parameters and patch instruction list (NB: there is also could be object fields)
398            AbstractInsnNode cur = constructor.instructions.getFirst();
399            while (cur != null) {
400                if (cur instanceof FieldInsnNode) {
401                    FieldInsnNode fieldNode = (FieldInsnNode) cur;
402                    String fieldName = fieldNode.name;
403                    if (fieldNode.getOpcode() == Opcodes.PUTFIELD && InlineCodegenUtil.isCapturedFieldName(fieldName)) {
404    
405                        boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode;
406                        boolean isPrevPrevVarNode = isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode;
407    
408                        if (isPrevPrevVarNode) {
409                            VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious();
410                            if (node.var == 0) {
411                                VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
412                                int varIndex = previous.var;
413                                LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
414                                String newFieldName = isThis0(fieldName) && shouldRenameThis0(parentFieldRemapper, indexToLambda.values()) ? getNewFieldName(fieldName, true) : fieldName;
415                                CapturedParamInfo info = capturedParamBuilder.addCapturedParam(owner, fieldName, newFieldName, Type.getType(fieldNode.desc), lambdaInfo != null, null);
416                                if (lambdaInfo != null) {
417                                    info.setLambda(lambdaInfo);
418                                    capturedLambdas.add(lambdaInfo);
419                                }
420                                constructorAdditionalFakeParams.add(info);
421                                capturedParams.add(varIndex);
422    
423                                constructor.instructions.remove(previous.getPrevious());
424                                constructor.instructions.remove(previous);
425                                AbstractInsnNode temp = cur;
426                                cur = cur.getNext();
427                                constructor.instructions.remove(temp);
428                                continue;
429                            }
430                        }
431                    }
432                }
433                cur = cur.getNext();
434            }
435    
436            constructorParamBuilder.addThis(oldObjectType, false);
437            String constructorDesc = anonymousObjectGen.getConstructorDesc();
438    
439            if (constructorDesc == null) {
440                // in case of anonymous object with empty closure
441                constructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE);
442            }
443    
444            Type [] types = Type.getArgumentTypes(constructorDesc);
445            for (Type type : types) {
446                LambdaInfo info = indexToLambda.get(constructorParamBuilder.getNextValueParameterIndex());
447                ParameterInfo parameterInfo = constructorParamBuilder.addNextParameter(type, info != null, null);
448                parameterInfo.setLambda(info);
449                if (capturedParams.contains(parameterInfo.getIndex())) {
450                    parameterInfo.setCaptured(true);
451                } else {
452                    //otherwise it's super constructor parameter
453                }
454            }
455    
456            //For all inlined lambdas add their captured parameters
457            //TODO: some of such parameters could be skipped - we should perform additional analysis
458            Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
459            List<CapturedParamDesc> allRecapturedParameters = new ArrayList<CapturedParamDesc>();
460            boolean addCapturedNotAddOuter = parentFieldRemapper.isRoot() || (parentFieldRemapper instanceof InlinedLambdaRemapper && parentFieldRemapper.getParent().isRoot());
461            Map<String, CapturedParamInfo> alreadyAdded = new HashMap<String, CapturedParamInfo>();
462            for (LambdaInfo info : capturedLambdas) {
463                if (addCapturedNotAddOuter) {
464                    for (CapturedParamDesc desc : info.getCapturedVars()) {
465                        String key = desc.getFieldName() + "$$$" + desc.getType().getClassName();
466                        CapturedParamInfo alreadyAddedParam = alreadyAdded.get(key);
467    
468                        CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(
469                                desc,
470                                alreadyAddedParam != null ? alreadyAddedParam.getNewFieldName() : getNewFieldName(desc.getFieldName(), false));
471                        StackValue composed = StackValue.field(desc.getType(),
472                                                               oldObjectType, /*TODO owner type*/
473                                                               recapturedParamInfo.getNewFieldName(),
474                                                               false,
475                                                               StackValue.LOCAL_0);
476                        recapturedParamInfo.setRemapValue(composed);
477                        allRecapturedParameters.add(desc);
478    
479                        constructorParamBuilder.addCapturedParam(recapturedParamInfo, recapturedParamInfo.getNewFieldName()).setRemapValue(composed);
480                        if (alreadyAddedParam != null) {
481                            recapturedParamInfo.setSkipInConstructor(true);
482                        }
483    
484                        if (isThis0(desc.getFieldName())) {
485                            alreadyAdded.put(key, recapturedParamInfo);
486                        }
487                    }
488                }
489                capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
490            }
491    
492            if (parentFieldRemapper instanceof InlinedLambdaRemapper && !capturedLambdas.isEmpty() && !addCapturedNotAddOuter) {
493                //lambda with non InlinedLambdaRemapper already have outer
494                FieldRemapper parent = parentFieldRemapper.getParent();
495                assert parent instanceof RegeneratedLambdaFieldRemapper;
496                final Type ownerType = Type.getObjectType(parent.getLambdaInternalName());
497    
498                CapturedParamDesc desc = new CapturedParamDesc(new CapturedParamOwner() {
499                    @Override
500                    public Type getType() {
501                        return ownerType;
502                    }
503                }, InlineCodegenUtil.THIS, ownerType);
504                CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(desc, InlineCodegenUtil.THIS$0/*outer lambda/object*/);
505                StackValue composed = StackValue.LOCAL_0;
506                recapturedParamInfo.setRemapValue(composed);
507                allRecapturedParameters.add(desc);
508    
509                constructorParamBuilder.addCapturedParam(recapturedParamInfo, recapturedParamInfo.getNewFieldName()).setRemapValue(composed);
510            }
511    
512            anonymousObjectGen.setAllRecapturedParameters(allRecapturedParameters);
513            anonymousObjectGen.setCapturedLambdasToInline(capturedLambdasToInline);
514    
515            return constructorAdditionalFakeParams;
516        }
517    
518        private static boolean shouldRenameThis0(@NotNull FieldRemapper parentFieldRemapper, Collection<LambdaInfo> values) {
519            if (isFirstDeclSiteLambdaFieldRemapper(parentFieldRemapper)) {
520                for (LambdaInfo value : values) {
521                    for (CapturedParamDesc desc : value.getCapturedVars()) {
522                        if (isThis0(desc.getFieldName())) {
523                            return true;
524                        }
525                    }
526                }
527            }
528            return false;
529        }
530    
531        @NotNull
532        public String getNewFieldName(@NotNull String oldName, boolean originalField) {
533            if (InlineCodegenUtil.THIS$0.equals(oldName)) {
534                if (!originalField) {
535                    return oldName;
536                } else {
537                    //rename original 'this$0' in declaration site lambda (inside inline function) to use this$0 only for outer lambda/object access on call site
538                    return addUniqueField(oldName + InlineCodegenUtil.INLINE_FUN_THIS_0_SUFFIX);
539                }
540            }
541            return addUniqueField(oldName + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX);
542        }
543    
544        @NotNull
545        private String addUniqueField(@NotNull String name) {
546            List<String> existNames = fieldNames.get(name);
547            if (existNames == null) {
548                existNames = new LinkedList<String>();
549                fieldNames.put(name, existNames);
550            }
551            String suffix = existNames.isEmpty() ? "" : "$" + existNames.size();
552            String newName = name + suffix;
553            existNames.add(newName);
554            return newName;
555        }
556    
557        private static boolean isFirstDeclSiteLambdaFieldRemapper(FieldRemapper parentRemapper) {
558            return !(parentRemapper instanceof RegeneratedLambdaFieldRemapper) && !(parentRemapper instanceof InlinedLambdaRemapper);
559        }
560    }