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