@Override
	@SuppressWarnings({"IfCanBeSwitch", "OptionalGetWithoutIsPresent"}) // switch changes semantics slightly
	public void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,
	                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,
	                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {
		boolean dirty = false;
		String className = initialClassState.getName();
		ClassNode node = context.getNode(bundle, initialClassState);
		for (MethodNode method : node.methods) {
			// Skip if abstract.
			InsnList instructions = method.instructions;
			if (instructions == null)
				continue;

			// Populate local access state.
			Int2ObjectMap<LocalAccessState> states = new Int2ObjectArrayMap<>();
			Int2BooleanArrayMap canOverwrite = new Int2BooleanArrayMap();

			// Populate local state from parameters.
			boolean isStatic = AccessFlag.isStatic(method.access);
			if (!isStatic) {
				// implicit 'this'
				ObjectValue thisValue = ObjectValue.object(Type.getObjectType(node.name), Nullness.NOT_NULL);
				LocalAccessState thisState = new LocalAccessState(0, thisValue);
				thisState.addWrite(-1, new VarInsnNode(ASTORE, 0));
				states.put(key(0, Type.OBJECT), thisState);
			}
			int paramSlot = isStatic ? 0 : 1;
			for (Type argumentType : Type.getArgumentTypes(method.desc)) {
				// explicit parameters
				ReValue paramValue = Unchecked.get(() -> ReValue.ofType(argumentType, Nullness.UNKNOWN));
				LocalAccessState paramState = new LocalAccessState(paramSlot, paramValue);
				paramState.addWrite(-1, createVarStore(paramSlot, argumentType));
				states.put(key(paramSlot, argumentType.getSort()), paramState);
				paramSlot += argumentType.getSize();
			}

			// Populate the rest of the state from the method code.
			Frame<ReValue>[] frames = context.analyze(inheritanceGraph, node, method);
			try {
				boolean controlFlowObserved = false;
				for (int i = 0; i < instructions.size(); i++) {
					AbstractInsnNode insn = instructions.get(i);
					int op = insn.getOpcode();

					// Record variable reads
					if (isVarLoad(op) && insn instanceof VarInsnNode vin) {
						Frame<ReValue> frame = frames[i];
						if (frame == null)
							continue; // Skip dead code
						ReValue top = frame.getStack(frame.getStackSize() - 1);
						LocalAccessState state = states.computeIfAbsent(key(vin.var, getTypeForVarInsn(vin).getSort()), k -> new LocalAccessState(vin.var));
						state.addRead(i, vin);
					}

					// Record variable writes
					else if (isVarStore(op) && insn instanceof VarInsnNode vin) {
						Frame<ReValue> frame = frames[i];
						if (frame == null)
							continue; // Skip dead code
						ReValue top = frame.getStack(frame.getStackSize() - 1);
						LocalAccessState state = states.computeIfAbsent(key(vin.var, getTypeForVarInsn(vin).getSort()), k -> new LocalAccessState(vin.var));
						state.addWrite(i, vin);

						// If we're moving forward in the method from the beginning, and haven't seen
						// the variable being used yet or any kind of control flow, then we can safely
						// just replace the state with the value on the stack instead of merging it.
						if (state.getReadsUpTo(i).isEmpty() && !controlFlowObserved)
							state.setState(top);
						else
							state.mergeState(top);
					}

					// Record variable mutations (iinc is a fun edge case...)
					else if (op == IINC && insn instanceof IincInsnNode iinc) {
						if (frames[i] == null)
							continue; // Skip dead code
						LocalAccessState state = states.computeIfAbsent(key(iinc.var, Type.INT), k -> new LocalAccessState(iinc.var));

						// Edge case: iinc counts as a read and write at the same time.
						state.addRead(i, iinc);
						state.addWrite(i, iinc);
						state.mergeState(iinc);
					}

					// Other state tracking which can change how we merge state
					else if (isFlowControl(insn)) {
						controlFlowObserved = true;
					}
				}
			} catch (IllegalValueException ex) {
				throw new TransformationException("Failed to create merged state for locals", ex);
			}

			// Remove locals that are not being used.
			for (int i = instructions.size() - 1; i >= 0; i--) {
				AbstractInsnNode insn = instructions.get(i);
				int op = insn.getOpcode();
				if (isVarLoad(op) && insn instanceof VarInsnNode vin) {
					// Replace variable loads with constant values if possible.
					Type varType = getTypeForVarInsn(vin);
					LocalAccessState state = states.get(key(vin.var, varType.getSort()));
					if (state != null && state.isEffectiveConstant()) {
						Frame<ReValue> frame = frames[i];
						if (frame == null)
							continue; // Skip dead code
						ReValue localValue = frame.getLocal(vin.var);
						if (localValue.hasKnownValue()) {
							AbstractInsnNode replacement = switch (localValue) {
								case DoubleValue doubleValue -> doubleToInsn(doubleValue.value().getAsDouble());
								case FloatValue floatValue -> floatToInsn((float) floatValue.value().getAsDouble());
								case IntValue intValue -> intToInsn(intValue.value().getAsInt());
								case LongValue longValue -> longToInsn(longValue.value().getAsLong());
								case StringValue stringValue -> new LdcInsnNode(stringValue.getText().get());
								default -> null;
							};

							// Mark dirty if the instruction was replaced.
							if (replacement != null) {
								instructions.set(vin, replacement);
								dirty = true;
							}
						}
					}
				} else if (isVarStore(op) && insn instanceof VarInsnNode vin) {
					// Remove variable writes if:
					// - They are never read from
					// - They are read from, but only used with a single known constant value (which we inline above).
					Type varType = getTypeForVarInsn(vin);
					LocalAccessState state = states.get(key(vin.var, varType.getSort()));
					if (state != null && (state.getReads().isEmpty() || state.isEffectiveConstant())) {
						instructions.set(vin, new InsnNode(varType.getSize() == 1 ? POP : POP2));
						dirty = true;
					}
				}
			}
		}
		if (dirty)
			context.setNode(bundle, initialClassState, node);
	}
A transformer that folds redundant variable use.
<br>
You should use {@link StackOperationFoldingTransformer} after using this for {@code POP} cleanup.

@author Matt Coley
recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/VariableFoldingTransformer.java