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                    InlineCodegenUtil.assertVersionNotGreaterThanJava6(version, name);
092                    super.visit(version, access, name, signature, superName, interfaces);
093                }
094    
095                @Override
096                public void visitInnerClass(@NotNull String name, String outerName, String innerName, int access) {
097                    innerClassNodes.add(new InnerClassNode(name, outerName, innerName, access));
098                }
099    
100                @Override
101                public MethodVisitor visitMethod(
102                        int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions
103                ) {
104                    MethodNode node = new MethodNode(access, name, desc, signature, exceptions);
105                    if (name.equals("<init>")){
106                        if (constructor != null)
107                            throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor");
108    
109                        constructor = node;
110                    } else {
111                        methodsToTransform.add(node);
112                    }
113                    return node;
114                }
115    
116                @Override
117                public FieldVisitor visitField(
118                        int access, @NotNull String name, @NotNull String desc, String signature, Object value
119                ) {
120                    addUniqueField(name);
121                    if (InlineCodegenUtil.isCapturedFieldName(name)) {
122                        return null;
123                    } else {
124                        return super.visitField(access, name, desc, signature, value);
125                    }
126                }
127    
128                @Override
129                public void visitSource(String source, String debug) {
130                    sourceInfo = source;
131                    debugInfo = debug;
132                }
133    
134                @Override
135                public void visitEnd() {
136    
137                }
138            }, ClassReader.SKIP_FRAMES);
139    
140            if (!inliningContext.isInliningLambda) {
141                if (debugInfo != null && !debugInfo.isEmpty()) {
142                    sourceMapper = SourceMapper.Companion.createFromSmap(SMAPParser.parse(debugInfo));
143                }
144                else {
145                    //seems we can't do any clever mapping cause we don't know any about original class name
146                    sourceMapper = IdenticalSourceMapper.INSTANCE;
147                }
148                if (sourceInfo != null && !InlineCodegenUtil.GENERATE_SMAP) {
149                    classBuilder.visitSource(sourceInfo, debugInfo);
150                }
151            }
152            else {
153                if (sourceInfo != null) {
154                    classBuilder.visitSource(sourceInfo, debugInfo);
155                }
156                sourceMapper = IdenticalSourceMapper.INSTANCE;
157            }
158    
159            ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
160            ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
161            List<CapturedParamInfo> additionalFakeParams =
162                    extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder,
163                                                                anonymousObjectGen, parentRemapper);
164            List<MethodVisitor> deferringMethods = new ArrayList<MethodVisitor>();
165    
166            for (MethodNode next : methodsToTransform) {
167                MethodVisitor deferringVisitor = newMethod(classBuilder, next);
168                InlineResult funResult =
169                        inlineMethodAndUpdateGlobalResult(anonymousObjectGen, parentRemapper, deferringVisitor, next, allCapturedParamBuilder, false);
170    
171                Type returnType = Type.getReturnType(next.desc);
172                if (!AsmUtil.isPrimitive(returnType)) {
173                    String oldFunReturnType = returnType.getInternalName();
174                    String newFunReturnType = funResult.getChangedTypes().get(oldFunReturnType);
175                    if (newFunReturnType != null) {
176                        inliningContext.typeRemapper.addAdditionalMappings(oldFunReturnType, newFunReturnType);
177                    }
178                }
179                deferringMethods.add(deferringVisitor);
180            }
181    
182            for (MethodVisitor method : deferringMethods) {
183                method.visitEnd();
184            }
185    
186            generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, anonymousObjectGen, parentRemapper, additionalFakeParams);
187    
188            SourceMapper.Companion.flushToClassBuilder(sourceMapper, classBuilder);
189    
190            ClassVisitor visitor = classBuilder.getVisitor();
191            for (InnerClassNode node : innerClassNodes) {
192                visitor.visitInnerClass(node.name, node.outerName, node.innerName, node.access);
193            }
194    
195            writeOuterInfo(visitor);
196    
197            classBuilder.done();
198    
199            anonymousObjectGen.setNewLambdaType(newLambdaType);
200            return transformationResult;
201        }
202    
203        private void writeOuterInfo(@NotNull ClassVisitor visitor) {
204            InlineCallSiteInfo info = inliningContext.getCallSiteInfo();
205            visitor.visitOuterClass(info.getOwnerClassName(), info.getFunctionName(), info.getFunctionDesc());
206        }
207    
208        @NotNull
209        private InlineResult inlineMethodAndUpdateGlobalResult(
210                @NotNull AnonymousObjectGeneration anonymousObjectGen,
211                @NotNull FieldRemapper parentRemapper,
212                @NotNull MethodVisitor deferringVisitor,
213                @NotNull MethodNode next,
214                @NotNull ParametersBuilder allCapturedParamBuilder,
215                boolean isConstructor
216        ) {
217            InlineResult funResult = inlineMethod(anonymousObjectGen, parentRemapper, deferringVisitor, next, allCapturedParamBuilder, isConstructor);
218            transformationResult.addAllClassesToRemove(funResult);
219            transformationResult.getReifiedTypeParametersUsages().mergeAll(funResult.getReifiedTypeParametersUsages());
220            return funResult;
221        }
222    
223        @NotNull
224        private InlineResult inlineMethod(
225                @NotNull AnonymousObjectGeneration anonymousObjectGen,
226                @NotNull FieldRemapper parentRemapper,
227                @NotNull MethodVisitor deferringVisitor,
228                @NotNull MethodNode sourceNode,
229                @NotNull ParametersBuilder capturedBuilder,
230                boolean isConstructor
231        ) {
232            ReifiedTypeParametersUsages typeParametersToReify = inliningContext.reifedTypeInliner.reifyInstructions(sourceNode);
233            Parameters parameters = isConstructor ? capturedBuilder.buildParameters() : getMethodParametersWithCaptured(capturedBuilder, sourceNode);
234    
235            RegeneratedLambdaFieldRemapper remapper =
236                    new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(),
237                                                       parameters, anonymousObjectGen.getCapturedLambdasToInline(),
238                                                       parentRemapper, isConstructor);
239    
240            MethodInliner inliner =
241                    new MethodInliner(
242                            sourceNode,
243                            parameters,
244                            inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
245                            remapper,
246                            isSameModule,
247                            "Transformer for " + anonymousObjectGen.getOwnerInternalName(),
248                            sourceMapper,
249                            new InlineCallSiteInfo(
250                                    anonymousObjectGen.getOwnerInternalName(),
251                                    sourceNode.name,
252                                    isConstructor ? anonymousObjectGen.getNewConstructorDescriptor() : sourceNode.desc)
253                    );
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            //TODO for inline method make public class
293            anonymousObjectGen.setNewConstructorDescriptor(constructorDescriptor);
294            MethodVisitor constructorVisitor = classBuilder.newMethod(NO_ORIGIN,
295                                                                      AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
296                                                                      "<init>", constructorDescriptor,
297                                                                      null, ArrayUtil.EMPTY_STRING_ARRAY);
298    
299            //initialize captured fields
300            List<NewJavaField> newFieldsWithSkipped = TransformationUtilsKt.getNewFieldsToGenerate(allCapturedBuilder.listCaptured());
301            List<FieldInfo> fieldInfoWithSkipped = TransformationUtilsKt.transformToFieldInfo(newLambdaType, newFieldsWithSkipped);
302    
303            int paramIndex = 0;
304            InstructionAdapter capturedFieldInitializer = new InstructionAdapter(constructorVisitor);
305            for (int i = 0; i < fieldInfoWithSkipped.size(); i++) {
306                FieldInfo fieldInfo = fieldInfoWithSkipped.get(i);
307                if (!newFieldsWithSkipped.get(i).getSkip()) {
308                    AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, capturedIndexes[paramIndex], capturedFieldInitializer);
309                }
310                paramIndex++;
311            }
312    
313            //then transform constructor
314            //HACK: in inlinining into constructor we access original captured fields with field access not local var
315            //but this fields added to general params (this assumes local var access) not captured one,
316            //so we need to add them to captured params
317            for (CapturedParamInfo info : constructorAdditionalFakeParams) {
318                CapturedParamInfo fake = constructorInlineBuilder.addCapturedParamCopy(info);
319    
320                if (fake.getLambda() != null) {
321                    //set remap value to skip this fake (captured with lambda already skipped)
322                    StackValue composed = StackValue.field(fake.getType(),
323                                                           oldObjectType,
324                                                           fake.getNewFieldName(),
325                                                           false,
326                                                           StackValue.LOCAL_0);
327                    fake.setRemapValue(composed);
328                }
329            }
330    
331            inlineMethodAndUpdateGlobalResult(anonymousObjectGen, parentRemapper, capturedFieldInitializer, constructor, constructorInlineBuilder, true);
332            constructorVisitor.visitEnd();
333            AsmUtil.genClosureFields(TransformationUtilsKt.toNameTypePair(TransformationUtilsKt.filterSkipped(newFieldsWithSkipped)), classBuilder);
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    
352            return new RemappingClassBuilder(
353                    classBuilder,
354                    new AsmTypeRemapper(inliningContext.typeRemapper, inliningContext.getRoot().typeParameterMappings == null, transformationResult)
355            );
356        }
357    
358        @NotNull
359        private static DeferredMethodVisitor newMethod(@NotNull final ClassBuilder builder, @NotNull final MethodNode original) {
360            return new DeferredMethodVisitor(
361                    new MethodNode(original.access,
362                                   original.name,
363                                   original.desc,
364                                   original.signature,
365                                   ArrayUtil.toStringArray(original.exceptions)),
366    
367                    new Function0<MethodVisitor>() {
368                        @Override
369                        public MethodVisitor invoke() {
370                            return builder.newMethod(
371                                    NO_ORIGIN,
372                                    original.access,
373                                    original.name,
374                                    original.desc,
375                                    original.signature,
376                                    ArrayUtil.toStringArray(original.exceptions));
377                        }
378                    });
379        }
380    
381        private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
382                @NotNull MethodNode constructor,
383                @NotNull ParametersBuilder capturedParamBuilder,
384                @NotNull ParametersBuilder constructorParamBuilder,
385                @NotNull final AnonymousObjectGeneration anonymousObjectGen,
386                @NotNull FieldRemapper parentFieldRemapper
387        ) {
388    
389            CapturedParamOwner owner = new CapturedParamOwner() {
390                @Override
391                public Type getType() {
392                    return Type.getObjectType(anonymousObjectGen.getOwnerInternalName());
393                }
394            };
395    
396            Set<LambdaInfo> capturedLambdas = new LinkedHashSet<LambdaInfo>(); //captured var of inlined parameter
397            List<CapturedParamInfo> constructorAdditionalFakeParams = new ArrayList<CapturedParamInfo>();
398            Map<Integer, LambdaInfo> indexToLambda = anonymousObjectGen.getLambdasToInline();
399            Set<Integer> capturedParams = new HashSet<Integer>();
400    
401            //load captured parameters and patch instruction list (NB: there is also could be object fields)
402            AbstractInsnNode cur = constructor.instructions.getFirst();
403            while (cur != null) {
404                if (cur instanceof FieldInsnNode) {
405                    FieldInsnNode fieldNode = (FieldInsnNode) cur;
406                    String fieldName = fieldNode.name;
407                    if (fieldNode.getOpcode() == Opcodes.PUTFIELD && InlineCodegenUtil.isCapturedFieldName(fieldName)) {
408    
409                        boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode;
410                        boolean isPrevPrevVarNode = isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode;
411    
412                        if (isPrevPrevVarNode) {
413                            VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious();
414                            if (node.var == 0) {
415                                VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
416                                int varIndex = previous.var;
417                                LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
418                                String newFieldName = isThis0(fieldName) && shouldRenameThis0(parentFieldRemapper, indexToLambda.values()) ? getNewFieldName(fieldName, true) : fieldName;
419                                CapturedParamInfo info = capturedParamBuilder.addCapturedParam(owner, fieldName, newFieldName, Type.getType(fieldNode.desc), lambdaInfo != null, null);
420                                if (lambdaInfo != null) {
421                                    info.setLambda(lambdaInfo);
422                                    capturedLambdas.add(lambdaInfo);
423                                }
424                                constructorAdditionalFakeParams.add(info);
425                                capturedParams.add(varIndex);
426    
427                                constructor.instructions.remove(previous.getPrevious());
428                                constructor.instructions.remove(previous);
429                                AbstractInsnNode temp = cur;
430                                cur = cur.getNext();
431                                constructor.instructions.remove(temp);
432                                continue;
433                            }
434                        }
435                    }
436                }
437                cur = cur.getNext();
438            }
439    
440            constructorParamBuilder.addThis(oldObjectType, false);
441            String constructorDesc = anonymousObjectGen.getConstructorDesc();
442    
443            if (constructorDesc == null) {
444                // in case of anonymous object with empty closure
445                constructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE);
446            }
447    
448            Type [] types = Type.getArgumentTypes(constructorDesc);
449            for (Type type : types) {
450                LambdaInfo info = indexToLambda.get(constructorParamBuilder.getNextValueParameterIndex());
451                ParameterInfo parameterInfo = constructorParamBuilder.addNextParameter(type, info != null, null);
452                parameterInfo.setLambda(info);
453                if (capturedParams.contains(parameterInfo.getIndex())) {
454                    parameterInfo.setCaptured(true);
455                } else {
456                    //otherwise it's super constructor parameter
457                }
458            }
459    
460            //For all inlined lambdas add their captured parameters
461            //TODO: some of such parameters could be skipped - we should perform additional analysis
462            Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
463            List<CapturedParamDesc> allRecapturedParameters = new ArrayList<CapturedParamDesc>();
464            boolean addCapturedNotAddOuter = parentFieldRemapper.isRoot() || (parentFieldRemapper instanceof InlinedLambdaRemapper && parentFieldRemapper.getParent().isRoot());
465            Map<String, CapturedParamInfo> alreadyAdded = new HashMap<String, CapturedParamInfo>();
466            for (LambdaInfo info : capturedLambdas) {
467                if (addCapturedNotAddOuter) {
468                    for (CapturedParamDesc desc : info.getCapturedVars()) {
469                        String key = desc.getFieldName() + "$$$" + desc.getType().getClassName();
470                        CapturedParamInfo alreadyAddedParam = alreadyAdded.get(key);
471    
472                        CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(
473                                desc,
474                                alreadyAddedParam != null ? alreadyAddedParam.getNewFieldName() : getNewFieldName(desc.getFieldName(), false));
475                        StackValue composed = StackValue.field(desc.getType(),
476                                                               oldObjectType, /*TODO owner type*/
477                                                               recapturedParamInfo.getNewFieldName(),
478                                                               false,
479                                                               StackValue.LOCAL_0);
480                        recapturedParamInfo.setRemapValue(composed);
481                        allRecapturedParameters.add(desc);
482    
483                        constructorParamBuilder.addCapturedParam(recapturedParamInfo, recapturedParamInfo.getNewFieldName()).setRemapValue(composed);
484                        if (alreadyAddedParam != null) {
485                            recapturedParamInfo.setSkipInConstructor(true);
486                        }
487    
488                        if (isThis0(desc.getFieldName())) {
489                            alreadyAdded.put(key, recapturedParamInfo);
490                        }
491                    }
492                }
493                capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
494            }
495    
496            if (parentFieldRemapper instanceof InlinedLambdaRemapper && !capturedLambdas.isEmpty() && !addCapturedNotAddOuter) {
497                //lambda with non InlinedLambdaRemapper already have outer
498                FieldRemapper parent = parentFieldRemapper.getParent();
499                assert parent instanceof RegeneratedLambdaFieldRemapper;
500                final Type ownerType = Type.getObjectType(parent.getLambdaInternalName());
501    
502                CapturedParamDesc desc = new CapturedParamDesc(new CapturedParamOwner() {
503                    @Override
504                    public Type getType() {
505                        return ownerType;
506                    }
507                }, InlineCodegenUtil.THIS, ownerType);
508                CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(desc, InlineCodegenUtil.THIS$0/*outer lambda/object*/);
509                StackValue composed = StackValue.LOCAL_0;
510                recapturedParamInfo.setRemapValue(composed);
511                allRecapturedParameters.add(desc);
512    
513                constructorParamBuilder.addCapturedParam(recapturedParamInfo, recapturedParamInfo.getNewFieldName()).setRemapValue(composed);
514            }
515    
516            anonymousObjectGen.setAllRecapturedParameters(allRecapturedParameters);
517            anonymousObjectGen.setCapturedLambdasToInline(capturedLambdasToInline);
518    
519            return constructorAdditionalFakeParams;
520        }
521    
522        private static boolean shouldRenameThis0(@NotNull FieldRemapper parentFieldRemapper, Collection<LambdaInfo> values) {
523            if (isFirstDeclSiteLambdaFieldRemapper(parentFieldRemapper)) {
524                for (LambdaInfo value : values) {
525                    for (CapturedParamDesc desc : value.getCapturedVars()) {
526                        if (isThis0(desc.getFieldName())) {
527                            return true;
528                        }
529                    }
530                }
531            }
532            return false;
533        }
534    
535        @NotNull
536        public String getNewFieldName(@NotNull String oldName, boolean originalField) {
537            if (InlineCodegenUtil.THIS$0.equals(oldName)) {
538                if (!originalField) {
539                    return oldName;
540                } else {
541                    //rename original 'this$0' in declaration site lambda (inside inline function) to use this$0 only for outer lambda/object access on call site
542                    return addUniqueField(oldName + InlineCodegenUtil.INLINE_FUN_THIS_0_SUFFIX);
543                }
544            }
545            return addUniqueField(oldName + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX);
546        }
547    
548        @NotNull
549        private String addUniqueField(@NotNull String name) {
550            List<String> existNames = fieldNames.get(name);
551            if (existNames == null) {
552                existNames = new LinkedList<String>();
553                fieldNames.put(name, existNames);
554            }
555            String suffix = existNames.isEmpty() ? "" : "$" + existNames.size();
556            String newName = name + suffix;
557            existNames.add(newName);
558            return newName;
559        }
560    
561        private static boolean isFirstDeclSiteLambdaFieldRemapper(FieldRemapper parentRemapper) {
562            return !(parentRemapper instanceof RegeneratedLambdaFieldRemapper) && !(parentRemapper instanceof InlinedLambdaRemapper);
563        }
564    }