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 }