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