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