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