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