| // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.verticalclassmerging; |
| |
| import static com.android.tools.r8.ir.conversion.ExtraUnusedParameter.computeExtraUnusedParameters; |
| import static com.android.tools.r8.utils.MapUtils.ignoreKey; |
| |
| import com.android.tools.r8.classmerging.ClassMergerGraphLens; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexReference; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.lens.GraphLens; |
| import com.android.tools.r8.graph.lens.MethodLookupResult; |
| import com.android.tools.r8.graph.proto.ArgumentInfoCollection; |
| import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; |
| import com.android.tools.r8.ir.code.InvokeType; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.KeepInfoCollection; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.IterableUtils; |
| import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap; |
| import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap; |
| import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap; |
| import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap; |
| import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap; |
| import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Streams; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| // This graph lens is instantiated during vertical class merging. The graph lens is context |
| // sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke- |
| // super vs invoke-virtual). This is illustrated by the following example. |
| // |
| // public class A { |
| // public void m() { ... } |
| // } |
| // public class B extends A { |
| // @Override |
| // public void m() { invoke-super A.m(); ... } |
| // |
| // public void m2() { invoke-virtual A.m(); ... } |
| // } |
| // |
| // Vertical class merging will merge class A into class B. Since class B already has a method with |
| // the signature "void B.m()", the method A.m will be given a fresh name and moved to class B. |
| // During this process, the method corresponding to A.m will be made private such that it can be |
| // called via an invoke-direct instruction. |
| // |
| // For the invocation "invoke-super A.m()" in B.m, this graph lens will return the newly created, |
| // private method corresponding to A.m (that is now in B.m with a fresh name), such that the |
| // invocation will hit the same implementation as the original super.m() call. |
| // |
| // For the invocation "invoke-virtual A.m()" in B.m2, this graph lens will return the method B.m. |
| public class VerticalClassMergerGraphLens extends ClassMergerGraphLens { |
| |
| private final VerticallyMergedClasses mergedClasses; |
| private final Map<DexType, Map<DexMethod, DexMethod>> contextualSuperToImplementationInContexts; |
| private final BidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures; |
| |
| private final Set<DexMethod> staticizedMethods; |
| |
| private VerticalClassMergerGraphLens( |
| AppView<?> appView, |
| VerticallyMergedClasses mergedClasses, |
| BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap, |
| Map<DexType, Map<DexMethod, DexMethod>> contextualSuperToImplementationInContexts, |
| BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures, |
| BidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures, |
| Set<DexMethod> staticizedMethods) { |
| super( |
| appView, |
| fieldMap, |
| Collections.emptyMap(), |
| mergedClasses.getBidirectionalMap(), |
| newMethodSignatures); |
| this.mergedClasses = mergedClasses; |
| this.contextualSuperToImplementationInContexts = contextualSuperToImplementationInContexts; |
| this.extraNewMethodSignatures = extraNewMethodSignatures; |
| this.staticizedMethods = staticizedMethods; |
| } |
| |
| public boolean hasInterfaceBeenMergedIntoClass(DexType type) { |
| return mergedClasses.hasInterfaceBeenMergedIntoClass(type); |
| } |
| |
| private boolean isMerged(DexMethod method) { |
| DexMethod previousMethod = getPreviousMethodSignature(method); |
| return mergedClasses.hasBeenMergedIntoSubtype(previousMethod.getHolderType()); |
| } |
| |
| private boolean isStaticized(DexMethod method) { |
| return staticizedMethods.contains(method); |
| } |
| |
| @Override |
| public boolean isVerticalClassMergerLens() { |
| return true; |
| } |
| |
| @Override |
| public VerticalClassMergerGraphLens asVerticalClassMergerLens() { |
| return this; |
| } |
| |
| @Override |
| public DexType getPreviousClassType(DexType type) { |
| return type; |
| } |
| |
| @Override |
| public DexField getNextFieldSignature(DexField previous) { |
| DexField field = super.getNextFieldSignature(previous); |
| assert field.verifyReferencedBaseTypesMatches( |
| type -> !mergedClasses.isMergeSource(type), dexItemFactory()); |
| return field; |
| } |
| |
| @Override |
| public DexMethod getNextMethodSignature(DexMethod previous) { |
| if (extraNewMethodSignatures.containsKey(previous)) { |
| return getNextImplementationMethodSignature(previous); |
| } |
| DexMethod method = super.getNextMethodSignature(previous); |
| assert method.verifyReferencedBaseTypesMatches( |
| type -> !mergedClasses.isMergeSource(type), dexItemFactory()); |
| return method; |
| } |
| |
| public DexMethod getNextBridgeMethodSignature(DexMethod previous) { |
| DexMethod method = newMethodSignatures.getRepresentativeValueOrDefault(previous, previous); |
| assert method.verifyReferencedBaseTypesMatches( |
| type -> !mergedClasses.isMergeSource(type), dexItemFactory()); |
| return method; |
| } |
| |
| public DexMethod getNextImplementationMethodSignature(DexMethod previous) { |
| DexMethod method = extraNewMethodSignatures.getRepresentativeValueOrDefault(previous, previous); |
| assert method.verifyReferencedBaseTypesMatches( |
| type -> !mergedClasses.isMergeSource(type), dexItemFactory()); |
| return method; |
| } |
| |
| @Override |
| protected Iterable<DexType> internalGetOriginalTypes(DexType previous) { |
| Collection<DexType> originalTypes = mergedClasses.getSourcesFor(previous); |
| Iterable<DexType> currentType = IterableUtils.singleton(previous); |
| if (originalTypes == null) { |
| return currentType; |
| } |
| return Iterables.concat(currentType, originalTypes); |
| } |
| |
| @Override |
| protected MethodLookupResult internalLookupMethod( |
| DexMethod reference, |
| DexMethod context, |
| InvokeType type, |
| GraphLens codeLens, |
| LookupMethodContinuation continuation) { |
| if (this == codeLens) { |
| MethodLookupResult lookupResult = |
| MethodLookupResult.builder(this, codeLens) |
| .setReboundReference(reference) |
| .setReference(reference) |
| .setType(type) |
| .build(); |
| return continuation.lookupMethod(lookupResult); |
| } |
| return super.internalLookupMethod(reference, context, type, codeLens, continuation); |
| } |
| |
| @Override |
| public MethodLookupResult internalDescribeLookupMethod( |
| MethodLookupResult previous, DexMethod context, GraphLens codeLens) { |
| assert context != null || verifyIsContextFreeForMethod(previous.getReference(), codeLens); |
| assert context == null || previous.getType() != null; |
| assert previous.hasReboundReference(); |
| MethodLookupResult lookupResult; |
| DexMethod implementationReference = getImplementationTargetForInvokeSuper(previous, context); |
| if (implementationReference != null) { |
| lookupResult = |
| MethodLookupResult.builder(this, codeLens) |
| .setReboundReference(implementationReference) |
| .setReference(implementationReference) |
| .setPrototypeChanges( |
| internalDescribePrototypeChanges( |
| previous.getPrototypeChanges(), |
| previous.getReboundReference(), |
| implementationReference)) |
| .setType( |
| isStaticized(implementationReference) ? InvokeType.STATIC : InvokeType.VIRTUAL) |
| .build(); |
| } else { |
| DexMethod newReboundReference = previous.getRewrittenReboundReference(newMethodSignatures); |
| assert !appView.testing().enableVerticalClassMergerLensAssertion |
| || newReboundReference.verifyReferencedBaseTypesMatches( |
| type -> !mergedClasses.isMergeSource(type), dexItemFactory()); |
| DexMethod newReference = |
| previous.getRewrittenReferenceFromRewrittenReboundReference( |
| newReboundReference, this::getNextClassType, dexItemFactory()); |
| lookupResult = |
| MethodLookupResult.builder(this, codeLens) |
| .setReboundReference(newReboundReference) |
| .setReference(newReference) |
| .setType(mapInvocationType(newReference, previous.getReference(), previous.getType())) |
| .setPrototypeChanges( |
| internalDescribePrototypeChanges( |
| previous.getPrototypeChanges(), |
| previous.getReboundReference(), |
| newReboundReference)) |
| .build(); |
| } |
| assert !appView.testing().enableVerticalClassMergerLensAssertion |
| || Streams.stream(lookupResult.getReference().getReferencedBaseTypes(dexItemFactory())) |
| .noneMatch(mergedClasses::hasBeenMergedIntoSubtype); |
| return lookupResult; |
| } |
| |
| private DexMethod getImplementationTargetForInvokeSuper( |
| MethodLookupResult previous, DexMethod context) { |
| if (previous.getType().isSuper() && !isMerged(context)) { |
| return contextualSuperToImplementationInContexts |
| .getOrDefault(context.getHolderType(), Collections.emptyMap()) |
| .get(previous.getReference()); |
| } |
| return null; |
| } |
| |
| @Override |
| protected RewrittenPrototypeDescription internalDescribePrototypeChanges( |
| RewrittenPrototypeDescription prototypeChanges, |
| DexMethod previousMethod, |
| DexMethod newMethod) { |
| if (isStaticized(newMethod)) { |
| // The receiver has been added as an explicit argument. |
| assert newMethod.getArity() == previousMethod.getArity() + 1; |
| RewrittenPrototypeDescription isConvertedToStaticMethod = |
| RewrittenPrototypeDescription.createForArgumentsInfo( |
| ArgumentInfoCollection.builder() |
| .setArgumentInfosSize(newMethod.getParameters().size()) |
| .setIsConvertedToStaticMethod() |
| .build()); |
| return prototypeChanges.combine(isConvertedToStaticMethod); |
| } |
| if (newMethod.getArity() > previousMethod.getArity()) { |
| assert dexItemFactory().isConstructor(previousMethod); |
| RewrittenPrototypeDescription collisionResolution = |
| RewrittenPrototypeDescription.createForExtraParameters( |
| computeExtraUnusedParameters(previousMethod, newMethod)); |
| return prototypeChanges.combine(collisionResolution); |
| } |
| assert newMethod.getArity() == previousMethod.getArity(); |
| return prototypeChanges; |
| } |
| |
| @Override |
| public DexMethod getPreviousMethodSignature(DexMethod method) { |
| return super.getPreviousMethodSignature( |
| extraNewMethodSignatures.getRepresentativeKeyOrDefault(method, method)); |
| } |
| |
| @Override |
| public DexMethod getPreviousMethodSignatureForMapping(DexMethod method) { |
| DexMethod orDefault = newMethodSignatures.getRepresentativeKeyOrDefault(method, method); |
| return super.getPreviousMethodSignature(orDefault); |
| } |
| |
| @Override |
| protected InvokeType mapInvocationType( |
| DexMethod newMethod, DexMethod previousMethod, InvokeType type) { |
| if (isStaticized(newMethod)) { |
| return InvokeType.STATIC; |
| } |
| if (type.isInterface() |
| && mergedClasses.hasInterfaceBeenMergedIntoClass(previousMethod.getHolderType())) { |
| DexClass newMethodHolder = appView.definitionForHolder(newMethod); |
| if (newMethodHolder != null && !newMethodHolder.isInterface()) { |
| return InvokeType.VIRTUAL; |
| } |
| } |
| return type; |
| } |
| |
| @Override |
| public boolean isContextFreeForMethods(GraphLens codeLens) { |
| if (codeLens == this) { |
| return true; |
| } |
| return contextualSuperToImplementationInContexts.isEmpty() |
| && getPrevious().isContextFreeForMethods(codeLens); |
| } |
| |
| @Override |
| public boolean verifyIsContextFreeForMethod(DexMethod method, GraphLens codeLens) { |
| if (codeLens == this) { |
| return true; |
| } |
| assert getPrevious().verifyIsContextFreeForMethod(method, codeLens); |
| DexMethod previous = getPrevious().lookupMethod(method, null, null, codeLens).getReference(); |
| assert contextualSuperToImplementationInContexts.values().stream() |
| .noneMatch(virtualToDirectMethodMap -> virtualToDirectMethodMap.containsKey(previous)); |
| return true; |
| } |
| |
| public boolean assertPinnedNotModified(AppView<AppInfoWithLiveness> appView) { |
| KeepInfoCollection keepInfo = appView.getKeepInfo(); |
| InternalOptions options = appView.options(); |
| keepInfo.forEachPinnedType(this::assertReferenceNotModified, options); |
| keepInfo.forEachPinnedMethod(this::assertReferenceNotModified, options); |
| keepInfo.forEachPinnedField(this::assertReferenceNotModified, options); |
| return true; |
| } |
| |
| private void assertReferenceNotModified(DexReference reference) { |
| if (reference.isDexField()) { |
| DexField field = reference.asDexField(); |
| assert getNextFieldSignature(field).isIdenticalTo(field); |
| } else if (reference.isDexMethod()) { |
| DexMethod method = reference.asDexMethod(); |
| assert getNextMethodSignature(method).isIdenticalTo(method); |
| } else { |
| assert reference.isDexType(); |
| DexType type = reference.asDexType(); |
| assert getNextClassType(type).isIdenticalTo(type); |
| } |
| } |
| |
| public static class Builder |
| extends BuilderBase<VerticalClassMergerGraphLens, VerticallyMergedClasses> { |
| |
| protected final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures = |
| new BidirectionalOneToOneHashMap<>(); |
| protected final Map<DexField, DexField> pendingNewFieldSignatureUpdates = |
| new IdentityHashMap<>(); |
| |
| private final Map<DexType, Map<DexMethod, DexMethod>> |
| contextualSuperToImplementationInContexts = new IdentityHashMap<>(); |
| |
| private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> |
| newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap(); |
| private final Map<DexMethod, DexMethod> pendingNewMethodSignatureUpdates = |
| new IdentityHashMap<>(); |
| |
| private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures = |
| new BidirectionalOneToOneHashMap<>(); |
| private final Map<DexMethod, DexMethod> pendingExtraNewMethodSignatureUpdates = |
| new IdentityHashMap<>(); |
| |
| private final Set<DexMethod> staticizedMethods = Sets.newIdentityHashSet(); |
| private final Map<DexMethod, DexMethod> pendingStaticizedMethodUpdates = |
| new IdentityHashMap<>(); |
| |
| static Builder createBuilderForFixup(VerticalClassMergerResult verticalClassMergerResult) { |
| return verticalClassMergerResult.getLensBuilder(); |
| } |
| |
| @Override |
| public void commitPendingUpdates() { |
| // Commit new field signatures. |
| newFieldSignatures.putAll(pendingNewFieldSignatureUpdates); |
| pendingNewFieldSignatureUpdates.clear(); |
| |
| // Commit new method signatures. |
| Map<DexMethod, DexMethod> newMethodSignatureUpdates = new IdentityHashMap<>(); |
| Map<DexMethod, DexMethod> newMethodSignatureRepresentativeUpdates = new IdentityHashMap<>(); |
| pendingNewMethodSignatureUpdates.forEach( |
| (from, to) -> { |
| Set<DexMethod> originalMethodSignatures = newMethodSignatures.getKeys(from); |
| if (originalMethodSignatures.isEmpty()) { |
| newMethodSignatureUpdates.put(from, to); |
| } else { |
| for (DexMethod originalMethodSignature : originalMethodSignatures) { |
| newMethodSignatureUpdates.put(originalMethodSignature, to); |
| } |
| if (newMethodSignatures.hasExplicitRepresentativeKey(from)) { |
| assert originalMethodSignatures.size() > 1; |
| newMethodSignatureRepresentativeUpdates.put( |
| to, newMethodSignatures.getRepresentativeKey(from)); |
| } else { |
| assert originalMethodSignatures.size() == 1; |
| } |
| } |
| }); |
| newMethodSignatures.removeValues(pendingNewMethodSignatureUpdates.keySet()); |
| newMethodSignatures.putAll(newMethodSignatureUpdates); |
| newMethodSignatureRepresentativeUpdates.forEach( |
| (value, representative) -> { |
| assert newMethodSignatures.getKeys(value).size() > 1; |
| newMethodSignatures.setRepresentative(value, representative); |
| }); |
| pendingNewMethodSignatureUpdates.clear(); |
| |
| // Commit extra new method signatures. |
| extraNewMethodSignatures.putAll(pendingExtraNewMethodSignatureUpdates); |
| pendingExtraNewMethodSignatureUpdates.clear(); |
| |
| // Commit staticized methods. |
| staticizedMethods.removeAll(pendingStaticizedMethodUpdates.keySet()); |
| staticizedMethods.addAll(pendingStaticizedMethodUpdates.values()); |
| pendingStaticizedMethodUpdates.clear(); |
| } |
| |
| @Override |
| public void fixupField(DexField oldFieldSignature, DexField newFieldSignature) { |
| DexField originalFieldSignature = |
| newFieldSignatures.getKeyOrDefault(oldFieldSignature, oldFieldSignature); |
| pendingNewFieldSignatureUpdates.put(originalFieldSignature, newFieldSignature); |
| } |
| |
| @Override |
| public void fixupMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) { |
| if (extraNewMethodSignatures.containsValue(oldMethodSignature)) { |
| DexMethod originalMethodSignature = extraNewMethodSignatures.getKey(oldMethodSignature); |
| pendingExtraNewMethodSignatureUpdates.put(originalMethodSignature, newMethodSignature); |
| } else { |
| pendingNewMethodSignatureUpdates.put(oldMethodSignature, newMethodSignature); |
| } |
| |
| if (staticizedMethods.contains(oldMethodSignature)) { |
| pendingStaticizedMethodUpdates.put(oldMethodSignature, newMethodSignature); |
| } |
| } |
| |
| public void fixupContextualVirtualToDirectMethodMaps() { |
| for (Entry<DexType, Map<DexMethod, DexMethod>> entry : |
| contextualSuperToImplementationInContexts.entrySet()) { |
| for (Entry<DexMethod, DexMethod> innerEntry : entry.getValue().entrySet()) { |
| DexMethod virtualMethod = innerEntry.getValue(); |
| DexMethod implementationMethod = extraNewMethodSignatures.get(virtualMethod); |
| if (implementationMethod != null) { |
| // Handle invoke-super to non-private virtual method. |
| innerEntry.setValue(implementationMethod); |
| } else { |
| // Handle invoke-super to private virtual method (nest access). |
| assert newMethodSignatures.containsKey(virtualMethod); |
| innerEntry.setValue(newMethodSignatures.get(virtualMethod)); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public Set<DexMethod> getOriginalMethodReferences(DexMethod method) { |
| if (extraNewMethodSignatures.containsValue(method)) { |
| return Set.of(extraNewMethodSignatures.getKey(method)); |
| } |
| Set<DexMethod> previousMethodSignatures = newMethodSignatures.getKeys(method); |
| if (!previousMethodSignatures.isEmpty()) { |
| return previousMethodSignatures; |
| } |
| return Set.of(method); |
| } |
| |
| @Override |
| public VerticalClassMergerGraphLens build( |
| AppView<?> appView, VerticallyMergedClasses mergedClasses) { |
| // Build new graph lens. |
| assert !mergedClasses.isEmpty(); |
| return new VerticalClassMergerGraphLens( |
| appView, |
| mergedClasses, |
| newFieldSignatures, |
| contextualSuperToImplementationInContexts, |
| newMethodSignatures, |
| extraNewMethodSignatures, |
| staticizedMethods); |
| } |
| |
| public void recordMove(DexEncodedField from, DexEncodedField to) { |
| newFieldSignatures.put(from.getReference(), to.getReference()); |
| } |
| |
| public void recordMove(DexEncodedMethod from, DexEncodedMethod to) { |
| newMethodSignatures.put(from.getReference(), to.getReference()); |
| } |
| |
| public void recordSplit( |
| DexEncodedMethod from, |
| DexEncodedMethod override, |
| DexEncodedMethod bridge, |
| DexEncodedMethod implementation) { |
| if (override != null) { |
| assert bridge == null; |
| newMethodSignatures.put(from.getReference(), override.getReference()); |
| newMethodSignatures.put(override.getReference(), override.getReference()); |
| newMethodSignatures.setRepresentative(override.getReference(), override.getReference()); |
| } else { |
| assert bridge != null; |
| newMethodSignatures.put(from.getReference(), bridge.getReference()); |
| } |
| |
| if (implementation == null) { |
| return; |
| } |
| |
| extraNewMethodSignatures.put(from.getReference(), implementation.getReference()); |
| |
| if (implementation.isStatic()) { |
| staticizedMethods.add(implementation.getReference()); |
| } |
| } |
| |
| public void mapVirtualMethodToDirectInType( |
| DexMethod from, ProgramMethod to, DexProgramClass context) { |
| contextualSuperToImplementationInContexts |
| .computeIfAbsent(context.getType(), ignoreKey(IdentityHashMap::new)) |
| .put(from, to.getReference()); |
| } |
| |
| public void merge(VerticalClassMergerGraphLens.Builder builder) { |
| newFieldSignatures.putAll(builder.newFieldSignatures); |
| builder.newMethodSignatures.forEachManyToOneMapping( |
| (keys, value, representative) -> { |
| boolean isRemapping = |
| Iterables.any( |
| keys, |
| key -> newMethodSignatures.containsValue(key) && key.isNotIdenticalTo(value)); |
| if (isRemapping) { |
| // If I and J are merged into A and both I.m() and J.m() exists, then we may map J.m() |
| // to I.m() as a result of merging J into A, and then subsequently merge I.m() to |
| // A.m() as a result of merging I into A. |
| assert keys.size() == 1; |
| DexMethod key = keys.iterator().next(); |
| |
| // When merging J.m() to I.m() we create the mappings {I.m(), J.m()} -> I.m(). |
| DexMethod originalRepresentative = newMethodSignatures.getRepresentativeKey(key); |
| Set<DexMethod> originalKeys = newMethodSignatures.removeValue(key); |
| assert originalKeys.contains(key); |
| |
| // Now that I.m() is merged to A.m(), we modify the existing mappings into |
| // {I.m(), J.m()} -> A.m(). |
| newMethodSignatures.put(originalKeys, value); |
| newMethodSignatures.setRepresentative(value, originalRepresentative); |
| } else { |
| if (newMethodSignatures.containsValue(value) |
| && !newMethodSignatures.hasExplicitRepresentativeKey(value)) { |
| newMethodSignatures.setRepresentative( |
| value, newMethodSignatures.getRepresentativeKey(value)); |
| } |
| newMethodSignatures.put(keys, value); |
| if (keys.size() > 1 && !newMethodSignatures.hasExplicitRepresentativeKey(value)) { |
| newMethodSignatures.setRepresentative(value, representative); |
| } |
| } |
| }); |
| staticizedMethods.addAll(builder.staticizedMethods); |
| extraNewMethodSignatures.putAll(builder.extraNewMethodSignatures); |
| builder.contextualSuperToImplementationInContexts.forEach( |
| (key, value) -> |
| contextualSuperToImplementationInContexts |
| .computeIfAbsent(key, ignoreKey(IdentityHashMap::new)) |
| .putAll(value)); |
| } |
| } |
| } |