From 3b58e1a11539a6da05fa25f51e2acd91d2d9f7d7 Mon Sep 17 00:00:00 2001
From: Reinier Zwitserloot <reinier@zwitserloot.com>
Date: Sun, 7 Nov 2010 23:23:48 +0100
Subject: 'val' support for eclipse.

---
 .../lombok/eclipse/agent/EclipsePatcher.java       | 45 +++++++++++
 .../lombok/eclipse/agent/PatchFixes.java           | 92 +++++++++++++++++++++-
 2 files changed, 135 insertions(+), 2 deletions(-)

(limited to 'src/eclipseAgent')

diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
index 2ac24a48..e962ecbf 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
@@ -69,6 +69,8 @@ public class EclipsePatcher extends Agent {
 			patchPostCompileHookEcj(sm);
 		}
 		
+		patchEcjTransformers(sm);
+		
 		if (reloadExistingClasses) sm.reloadClasses(instrumentation);
 	}
 	
@@ -253,4 +255,47 @@ public class EclipsePatcher extends Agent {
 				.wrapMethod(new Hook("lombok.eclipse.TransformEclipseAST", "transform_swapped", "void", CUD_SIG, PARSER_SIG))
 				.request(StackRequest.THIS, StackRequest.RETURN_VALUE).build());
 	}
+	
+	private static void patchEcjTransformers(ScriptManager sm) {
+		patchHandleVal(sm);
+	}
+	
+	// Creates a copy of the 'initialization' field on a LocalDeclaration if the type of the LocalDeclaration is 'val', because the completion parser will null this out,
+	// which in turn stops us from inferring the intended type for 'val x = 5;'. We look at the copy.
+	// Also patches local declaration to not call .resolveType() on the initializer expression if we've already done so (calling it twice causes weird errors),
+	// and patches .resolve() on LocalDeclaration itself to just-in-time replace the 'val' vartype with the right one.
+	
+	private static void patchHandleVal(ScriptManager sm) {
+		final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration";
+		final String EXPRESSION_SIG = "org.eclipse.jdt.internal.compiler.ast.Expression";
+		final String BLOCKSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope";
+		final String PARSER_SIG = "org.eclipse.jdt.internal.compiler.parser.Parser";
+		final String TYPEBINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding";
+		
+		sm.addScript(ScriptBuilder.exitEarly()
+				.target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG))
+				.request(StackRequest.THIS, StackRequest.PARAM1)
+				.decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG))
+				.build());
+		
+		sm.addScript(ScriptBuilder.addField()
+				.fieldName("$initCopy")
+				.fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;")
+				.setPublic()
+				.setTransient()
+				.targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")
+				.build());
+		
+		sm.addScript(ScriptBuilder.wrapReturnValue()
+				.target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void"))
+				.request(StackRequest.THIS)
+				.wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "copyInitializationOfLocalDeclarationForVal", "void", PARSER_SIG))
+				.build());
+		
+		sm.addScript(ScriptBuilder.replaceMethodCall()
+				.target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG))
+				.methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG))
+				.replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG))
+				.build());
+	}
 }
diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java
index daf1cbf2..eef6fd9a 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java
@@ -31,10 +31,22 @@ import java.util.List;
 
 import lombok.core.DiagnosticsReceiver;
 import lombok.core.PostCompiler;
+import lombok.eclipse.Eclipse;
 
 import org.eclipse.jdt.core.IMethod;
-import org.eclipse.jdt.core.dom.ASTNode;
 import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.parser.Parser;
 
 public class PatchFixes {
 	public static int fixRetrieveStartingCatchPosition(int in) {
@@ -78,7 +90,7 @@ public class PatchFixes {
 		boolean isGenerated = internalNode.getClass().getField("$generatedBy").get(internalNode) != null;
 		if (isGenerated) {
 			domNode.getClass().getField("$isGenerated").set(domNode, true);
-			domNode.setFlags(domNode.getFlags() & ~ASTNode.ORIGINAL);
+			domNode.setFlags(domNode.getFlags() & ~org.eclipse.jdt.core.dom.ASTNode.ORIGINAL);
 		}
 	}
 	
@@ -127,4 +139,80 @@ public class PatchFixes {
 		String fileName = path + "/" + name;
 		return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE));
 	}
+	
+	public static void copyInitializationOfLocalDeclarationForVal(Parser parser) {
+		ASTNode[] astStack;
+		int astPtr;
+		try {
+			Field astStackF = Parser.class.getDeclaredField("astStack");
+			astStackF.setAccessible(true);
+			astStack = (ASTNode[]) astStackF.get(parser);
+			Field astPtrF = Parser.class.getDeclaredField("astPtr");
+			astPtrF.setAccessible(true);
+			astPtr = (Integer)astPtrF.get(parser);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr];
+		if (!(variableDecl instanceof LocalDeclaration)) return;
+		ASTNode init = variableDecl.initialization;
+		if (init == null) return;
+		if (variableDecl.type instanceof SingleTypeReference) {
+			SingleTypeReference ref = (SingleTypeReference) variableDecl.type;
+			if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return;
+		} else return;
+		
+		try {
+			LocalDeclaration.class.getDeclaredField("$initCopy").set(variableDecl, init);
+		} catch (Exception e) {
+			e.printStackTrace(System.out);
+			// In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception.
+		}
+	}
+	
+	private static Field initCopyField;
+	
+	static {
+		try {
+			initCopyField = LocalDeclaration.class.getDeclaredField("$initCopy");
+		} catch (Throwable t) {
+			 //ignore - no $generatedBy exists when running in ecj.
+		}
+	}
+	
+	public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) {
+		if (local.type instanceof SingleTypeReference) {
+			char[] token = ((SingleTypeReference)local.type).token;
+			if (token == null || token.length != 3) return false;
+			else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false;
+		} else return false;
+		
+		Expression init = local.initialization;
+		if (init == null && initCopyField != null) {
+			try {
+				init = (Expression) initCopyField.get(local);
+				System.out.println("copy = " + init);
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			}
+		}
+		
+		TypeReference replacement = null;
+		if (init != null) {
+			TypeBinding resolved = init.resolveType(scope);
+			if (resolved != null) {
+				replacement = Eclipse.makeType(resolved, local.type, false);
+			}
+		}
+		
+		local.modifiers |= ClassFileConstants.AccFinal;
+		local.type = replacement != null ? replacement : new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(local.type, 3));
+		
+		return false;
+	}
+	
+	public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) {
+		if (expr.resolvedType != null) return expr.resolvedType;
+		return expr.resolveType(scope);
+	}
 }
-- 
cgit