From f1cf083890c223bf7245190264438740ee724bc9 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Wed, 3 Nov 2010 00:42:58 +0100 Subject: val in java, including tests and javac resolution utilities. --- src/core/lombok/javac/JavacResolution.java | 480 +++++++++++++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 src/core/lombok/javac/JavacResolution.java (limited to 'src/core/lombok/javac/JavacResolution.java') diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java new file mode 100644 index 00000000..6c60b11e --- /dev/null +++ b/src/core/lombok/javac/JavacResolution.java @@ -0,0 +1,480 @@ +package lombok.javac; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.util.ArrayDeque; +import java.util.Map; + +import javax.lang.model.type.TypeKind; +import javax.tools.DiagnosticListener; + +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Type.CapturedType; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTags; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.MemberEnter; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; + +public class JavacResolution { + private final Attr attr; + private final LogDisabler logDisabler; + + public JavacResolution(Context context) { + attr = Attr.instance(context); + logDisabler = new LogDisabler(context); + } + /** + * During resolution, the resolver will emit resolution errors, but without appropriate file names and line numbers. If these resolution errors stick around + * then they will be generated AGAIN, this time with proper names and line numbers, at the end. Therefore, we want to suppress the logger. + */ + private static final class LogDisabler { + private final Log log; + private static final Field errWriterField, warnWriterField, noticeWriterField, dumpOnErrorField, promptOnErrorField, diagnosticListenerField; + private PrintWriter errWriter, warnWriter, noticeWriter; + private Boolean dumpOnError, promptOnError; + private DiagnosticListener contextDiagnosticListener, logDiagnosticListener; + private final Context context; + + // If this is true, the fields changed. Better to print weird error messages than to fail outright. + private static final boolean dontBother; + + static { + boolean z; + Field a = null, b = null, c = null, d = null, e = null, f = null; + try { + a = Log.class.getDeclaredField("errWriter"); + b = Log.class.getDeclaredField("warnWriter"); + c = Log.class.getDeclaredField("noticeWriter"); + d = Log.class.getDeclaredField("dumpOnError"); + e = Log.class.getDeclaredField("promptOnError"); + f = Log.class.getDeclaredField("diagListener"); + z = false; + a.setAccessible(true); + b.setAccessible(true); + c.setAccessible(true); + d.setAccessible(true); + e.setAccessible(true); + f.setAccessible(true); + } catch (Exception x) { + z = true; + } + + errWriterField = a; + warnWriterField = b; + noticeWriterField = c; + dumpOnErrorField = d; + promptOnErrorField = e; + diagnosticListenerField = f; + dontBother = z; + } + + LogDisabler(Context context) { + this.log = Log.instance(context); + this.context = context; + } + + boolean disableLoggers() { + contextDiagnosticListener = context.get(DiagnosticListener.class); + context.put(DiagnosticListener.class, (DiagnosticListener) null); + if (dontBother) return false; + boolean dontBotherInstance = false; + + PrintWriter dummyWriter = new PrintWriter(new OutputStream() { + @Override public void write(int b) throws IOException { + // Do nothing on purpose + } + }); + + if (!dontBotherInstance) try { + errWriter = (PrintWriter) errWriterField.get(log); + errWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + warnWriter = (PrintWriter) warnWriterField.get(log); + warnWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + noticeWriter = (PrintWriter) noticeWriterField.get(log); + noticeWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + dumpOnError = (Boolean) dumpOnErrorField.get(log); + dumpOnErrorField.set(log, false); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + promptOnError = (Boolean) promptOnErrorField.get(log); + promptOnErrorField.set(log, false); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + logDiagnosticListener = (DiagnosticListener) diagnosticListenerField.get(log); + diagnosticListenerField.set(log, null); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (dontBotherInstance) enableLoggers(); + return !dontBotherInstance; + } + + void enableLoggers() { + if (contextDiagnosticListener != null) { + context.put(DiagnosticListener.class, contextDiagnosticListener); + contextDiagnosticListener = null; + } + if (errWriter != null) try { + errWriterField.set(log, errWriter); + errWriter = null; + } catch (Exception e) {} + + if (warnWriter != null) try { + warnWriterField.set(log, warnWriter); + warnWriter = null; + } catch (Exception e) {} + + if (noticeWriter != null) try { + noticeWriterField.set(log, noticeWriter); + noticeWriter = null; + } catch (Exception e) {} + + if (dumpOnError != null) try { + dumpOnErrorField.set(log, dumpOnError); + dumpOnError = null; + } catch (Exception e) {} + + if (promptOnError != null) try { + promptOnErrorField.set(log, promptOnError); + promptOnError = null; + } catch (Exception e) {} + + if (logDiagnosticListener != null) try { + diagnosticListenerField.set(log, logDiagnosticListener); + logDiagnosticListener = null; + } catch (Exception e) {} + } + } + + private static final class EnvFinder extends JCTree.Visitor { + private Env env = null; + private Enter enter; + private MemberEnter memberEnter; + private JCTree copyAt = null; + + EnvFinder(Context context) { + this.enter = Enter.instance(context); + this.memberEnter = MemberEnter.instance(context); + } + + Env get() { + return env; + } + + JCTree copyAt() { + return copyAt; + } + + @Override public void visitTopLevel(JCCompilationUnit tree) { + if (copyAt != null) return; + env = enter.getTopLevelEnv(tree); + } + + @Override public void visitClassDef(JCClassDecl tree) { + if (copyAt != null) return; + // The commented out one leaves the 'lint' field unset, which causes NPEs during attrib. So, we use the other one. + //env = enter.classEnv((JCClassDecl) tree, env); + env = enter.getClassEnv(tree.sym); + } + + @Override public void visitMethodDef(JCMethodDecl tree) { + if (copyAt != null) return; + env = memberEnter.getMethodEnv(tree, env); + copyAt = tree; + } + + public void visitVarDef(JCVariableDecl tree) { + if (copyAt != null) return; + env = memberEnter.getInitEnv(tree, env); + copyAt = tree; + } + + @Override public void visitBlock(JCBlock tree) { + if (copyAt != null) return; + copyAt = tree; + } + } + +// /** +// * The {@code Env} object primarily tracks legal symbol names. i.e. its the lexical scope. To build it, we need to go from the top and drill down to the current node, +// * updating the {@code Env} object at each step. This TreeVisitor does that. Requires {@code enterTrees} to be called first (this is done before processors run, normally). +// */ +// private static final class EnvChainer extends JCTree.Visitor { +// private Env env = null; +// private Enter enter; +// private MemberEnter memberEnter; +// private Attr attr; +// private JCTree target; +// private boolean blocksAreInitializers; +// +// EnvChainer(Context context) { +// this.enter = Enter.instance(context); +// this.memberEnter = MemberEnter.instance(context); +// this.attr = Attr.instance(context); +// } +// +// Env get() { +// return env; +// } +// +// @Override public void visitTopLevel(JCCompilationUnit tree) { +// env = enter.getTopLevelEnv(tree); +// } +// +// @Override public void visitClassDef(JCClassDecl tree) { +// // The commented out one leaves the 'lint' field unset, which causes NPEs during attrib. So, we use the other one. +// //env = enter.classEnv((JCClassDecl) tree, env); +// env = enter.getClassEnv(tree.sym); +// blocksAreInitializers = true; +// } +// +// @Override public void visitMethodDef(JCMethodDecl tree) { +// env = memberEnter.getMethodEnv(tree, env); +// blocksAreInitializers = false; +// if (tree.body != null) visitBlock(tree.body); +// } +// +// @Override public void visitBlock(JCBlock tree) { +// if (blocksAreInitializers) attr.attribStat(tree, env); +// for (JCStatement stat : tree.stats) { +// if (stat == target) return; +// attr.attribStat(stat, env); +// } +// } +// +// @Override public void visitTree(JCTree tree) { +// // Do nothing +// } +// +// public void setTarget(JCTree target) { +// this.target = target; +// } +// }; + + public Map resolve(JavacNode node) { + ArrayDeque stack = new ArrayDeque(); + + { + JavacNode n = node; + while (n != null) { + stack.push(n.get()); + n = n.up(); + } + } + + logDisabler.disableLoggers(); + try { + EnvFinder finder = new EnvFinder(node.getContext()); + while (!stack.isEmpty()) stack.pop().accept(finder); + + TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node); + JCTree copy = mirrorMaker.copy(finder.copyAt()); + + attrib(copy, finder.get()); + return mirrorMaker.getOriginalToCopyMap(); + } finally { + logDisabler.enableLoggers(); + } + } + + private void attrib(JCTree tree, Env env) { + if (tree instanceof JCBlock) attr.attribStat(tree, env); + else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl)tree).body, env); + else if (tree instanceof JCVariableDecl) attr.attribStat(tree, env); + else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl"); + } + +// public void resolveUpTo(JavacNode statementNode) { +// ArrayDeque stack = new ArrayDeque(); +// +// { +// JavacNode n = statementNode; +// while (n != null) { +// stack.push(n.get()); +// n = n.up(); +// } +// } +// +// logDisabler.disableLoggers(); +// try { +// JCTree tree = stack.isEmpty() ? null : stack.pop(); +// while (!stack.isEmpty()) { +// JCTree target = stack.pop(); +// envChainer.setTarget(target); +// tree.accept(envChainer); +// tree = target; +// } +// if (tree != null) { +// envChainer.setTarget(null); +// tree.accept(envChainer); +// } +// +//// System.out.println("ATTRIBSTAT: " + attr.attribStat(statementNode.get(), envChainer.get())); +//// if (statementNode.get() instanceof JCVariableDecl) { +//// System.out.println("Force-tribbing expr"); +//// JCExpression init = ((JCVariableDecl)statementNode.get()).init; +//// System.out.println("ATTRIBEXPR: " + attr.attribExpr(init, envChainer.get(), Type.noType)); +//// System.out.println("TYPE: " + ((JCVariableDecl)statementNode.get()).init.type); +//// } +// } finally { +// logDisabler.enableLoggers(); +// } +// } +// +// public void resolveExpr(JCExpression expr) { +// logDisabler.disableLoggers(); +// try { +// attr.attribExpr(expr, envChainer.get(), Type.noType); +// } finally { +// logDisabler.enableLoggers(); +// } +// } + + public static class TypeNotConvertibleException extends Exception { + public TypeNotConvertibleException(String msg) { + super(msg); + } + } + + public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast) throws TypeNotConvertibleException { + return typeToJCTree(type, maker, ast, false); + } + + public static JCExpression createJavaLangObject(TreeMaker maker, JavacAST ast) { + JCExpression out = maker.Ident(ast.toName("java")); + out = maker.Select(out, ast.toName("lang")); + out = maker.Select(out, ast.toName("Object")); + return out; + } + + private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { + // NB: There's such a thing as maker.Type(type), but this doesn't work very well; it screws up anonymous classes, captures, and adds an extra prefix dot for some reason too. + // -- so we write our own take on that here. + + if (type.isPrimitive()) return primitiveToJCTree(type.getKind(), maker); + if (type.isErroneous()) throw new TypeNotConvertibleException("Type cannot be resolved"); + + TypeSymbol symbol = type.asElement(); + List generics = type.getTypeArguments(); + + JCExpression replacement = null; + + if (symbol == null) throw new TypeNotConvertibleException("Null or compound type"); + + if (symbol.name.len == 0) { + // Anonymous inner class + if (type instanceof ClassType) { + List ifaces = ((ClassType)type).interfaces_field; + Type supertype = ((ClassType)type).supertype_field; + if (ifaces != null && ifaces.length() == 1) { + return typeToJCTree(ifaces.get(0), maker, ast, allowCompound); + } + if (supertype != null) return typeToJCTree(supertype, maker, ast, allowCompound); + } + throw new TypeNotConvertibleException("Anonymous inner class"); + } + + if (type instanceof CapturedType) { + if (allowCompound) { + if (type.getLowerBound() == null || type.getLowerBound().tag == TypeTags.BOT) { + if (type.getUpperBound().toString().equals("java.lang.Object")) { + return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + } + return maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), typeToJCTree(type.getUpperBound(), maker, ast, false)); + } else { + return maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), typeToJCTree(type.getLowerBound(), maker, ast, false)); + } + } + if (type.getUpperBound() != null) { + return typeToJCTree(type.getUpperBound(), maker, ast, allowCompound); + } + + return createJavaLangObject(maker, ast); + } + + String qName = symbol.getQualifiedName().toString(); + if (qName.isEmpty()) throw new TypeNotConvertibleException("unknown type"); + if (qName.startsWith("<")) throw new TypeNotConvertibleException(qName); + String[] baseNames = symbol.getQualifiedName().toString().split("\\."); + replacement = maker.Ident(ast.toName(baseNames[0])); + for (int i = 1; i < baseNames.length; i++) { + replacement = maker.Select(replacement, ast.toName(baseNames[i])); + } + + if (generics != null && !generics.isEmpty()) { + List args = List.nil(); + for (Type t : generics) args = args.append(typeToJCTree(t, maker, ast, true)); + replacement = maker.TypeApply(replacement, args); + } + + return replacement; + } + + private static JCExpression primitiveToJCTree(TypeKind kind, TreeMaker maker) throws TypeNotConvertibleException { + switch (kind) { + case BYTE: + return maker.TypeIdent(TypeTags.BYTE); + case CHAR: + return maker.TypeIdent(TypeTags.CHAR); + case SHORT: + return maker.TypeIdent(TypeTags.SHORT); + case INT: + return maker.TypeIdent(TypeTags.INT); + case LONG: + return maker.TypeIdent(TypeTags.LONG); + case FLOAT: + return maker.TypeIdent(TypeTags.FLOAT); + case DOUBLE: + return maker.TypeIdent(TypeTags.DOUBLE); + case BOOLEAN: + return maker.TypeIdent(TypeTags.BOOLEAN); + case VOID: + return maker.TypeIdent(TypeTags.VOID); + case NULL: + case NONE: + case OTHER: + default: + throw new TypeNotConvertibleException("Nulltype"); + } + } +} -- cgit From 09cebbdc59c118bb31f7e2fdf328ed60a03385c7 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Nov 2010 15:30:51 +0100 Subject: 'val' in javac now errors out with an appropriate message on val x = { .. }, and arrays no longer cause "Symbol not found: Array" errors. --- src/core/lombok/javac/JavacResolution.java | 17 +++++++++++++++++ src/core/lombok/javac/handlers/HandleVal.java | 6 ++++++ 2 files changed, 23 insertions(+) (limited to 'src/core/lombok/javac/JavacResolution.java') diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 6c60b11e..14de1ff8 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -12,6 +12,7 @@ import javax.tools.DiagnosticListener; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.CapturedType; import com.sun.tools.javac.code.Type.ClassType; import com.sun.tools.javac.code.Type; @@ -388,6 +389,22 @@ public class JavacResolution { } private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { + int dims = 0; + Type type0 = type; + while (type0 instanceof ArrayType) { + dims++; + type0 = ((ArrayType)type0).elemtype; + } + + JCExpression result = typeToJCTree0(type0, maker, ast, allowCompound); + while (dims > 0) { + result = maker.TypeArray(result); + dims--; + } + return result; + } + + private static JCExpression typeToJCTree0(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { // NB: There's such a thing as maker.Type(type), but this doesn't work very well; it screws up anonymous classes, captures, and adds an extra prefix dot for some reason too. // -- so we write our own take on that here. diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index a6f22093..f806ae6c 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -33,6 +33,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCNewArray; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; @ProviderFor(JavacASTVisitor.class) @@ -57,6 +58,11 @@ public class HandleVal extends JavacASTAdapter { return; } + if (local.init instanceof JCNewArray && ((JCNewArray)local.init).elemtype == null) { + localNode.addError("'val' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); + return; + } + local.mods.flags |= Flags.FINAL; JCExpression oldVarType = local.vartype; local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); -- cgit From cc60afa1eb4b14998e72c4fd5adf9def32e0e0f8 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Nov 2010 22:32:22 +0100 Subject: 'val' now also works in foreach loops, on both javac and ecj / eclipse. --- src/core/lombok/eclipse/handlers/HandleVal.java | 10 +- src/core/lombok/javac/JavacResolution.java | 121 ++++----------------- src/core/lombok/javac/handlers/HandleVal.java | 18 +-- .../lombok/eclipse/agent/EclipsePatcher.java | 39 ++++++- .../lombok/eclipse/agent/PatchFixes.java | 121 +++++++++++++++++++-- 5 files changed, 180 insertions(+), 129 deletions(-) (limited to 'src/core/lombok/javac/JavacResolution.java') diff --git a/src/core/lombok/eclipse/handlers/HandleVal.java b/src/core/lombok/eclipse/handlers/HandleVal.java index 6fa39b10..b3cfa879 100644 --- a/src/core/lombok/eclipse/handlers/HandleVal.java +++ b/src/core/lombok/eclipse/handlers/HandleVal.java @@ -26,6 +26,7 @@ import lombok.eclipse.EclipseASTVisitor; import lombok.eclipse.EclipseNode; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.mangosdk.spi.ProviderFor; @@ -41,7 +42,14 @@ public class HandleVal extends EclipseASTAdapter { if (token == null || token.length != 3) return; else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return; - if (local.initialization == null) { + boolean variableOfForEach = false; + + if (localNode.directUp().get() instanceof ForeachStatement) { + ForeachStatement fs = (ForeachStatement) localNode.directUp().get(); + variableOfForEach = fs.elementVariable == local; + } + + if (local.initialization == null && !variableOfForEach) { localNode.addError("'val' on a local variable requires an initializer expression"); } diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 14de1ff8..e0eb436d 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -12,11 +12,13 @@ import javax.tools.DiagnosticListener; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.CapturedType; import com.sun.tools.javac.code.Type.ClassType; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.TypeTags; +import com.sun.tools.javac.code.Types; import com.sun.tools.javac.comp.Attr; import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Enter; @@ -234,64 +236,11 @@ public class JavacResolution { if (copyAt != null) return; copyAt = tree; } + + @Override public void visitTree(JCTree that) { + } } -// /** -// * The {@code Env} object primarily tracks legal symbol names. i.e. its the lexical scope. To build it, we need to go from the top and drill down to the current node, -// * updating the {@code Env} object at each step. This TreeVisitor does that. Requires {@code enterTrees} to be called first (this is done before processors run, normally). -// */ -// private static final class EnvChainer extends JCTree.Visitor { -// private Env env = null; -// private Enter enter; -// private MemberEnter memberEnter; -// private Attr attr; -// private JCTree target; -// private boolean blocksAreInitializers; -// -// EnvChainer(Context context) { -// this.enter = Enter.instance(context); -// this.memberEnter = MemberEnter.instance(context); -// this.attr = Attr.instance(context); -// } -// -// Env get() { -// return env; -// } -// -// @Override public void visitTopLevel(JCCompilationUnit tree) { -// env = enter.getTopLevelEnv(tree); -// } -// -// @Override public void visitClassDef(JCClassDecl tree) { -// // The commented out one leaves the 'lint' field unset, which causes NPEs during attrib. So, we use the other one. -// //env = enter.classEnv((JCClassDecl) tree, env); -// env = enter.getClassEnv(tree.sym); -// blocksAreInitializers = true; -// } -// -// @Override public void visitMethodDef(JCMethodDecl tree) { -// env = memberEnter.getMethodEnv(tree, env); -// blocksAreInitializers = false; -// if (tree.body != null) visitBlock(tree.body); -// } -// -// @Override public void visitBlock(JCBlock tree) { -// if (blocksAreInitializers) attr.attribStat(tree, env); -// for (JCStatement stat : tree.stats) { -// if (stat == target) return; -// attr.attribStat(stat, env); -// } -// } -// -// @Override public void visitTree(JCTree tree) { -// // Do nothing -// } -// -// public void setTarget(JCTree target) { -// this.target = target; -// } -// }; - public Map resolve(JavacNode node) { ArrayDeque stack = new ArrayDeque(); @@ -325,58 +274,26 @@ public class JavacResolution { else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl"); } -// public void resolveUpTo(JavacNode statementNode) { -// ArrayDeque stack = new ArrayDeque(); -// -// { -// JavacNode n = statementNode; -// while (n != null) { -// stack.push(n.get()); -// n = n.up(); -// } -// } -// -// logDisabler.disableLoggers(); -// try { -// JCTree tree = stack.isEmpty() ? null : stack.pop(); -// while (!stack.isEmpty()) { -// JCTree target = stack.pop(); -// envChainer.setTarget(target); -// tree.accept(envChainer); -// tree = target; -// } -// if (tree != null) { -// envChainer.setTarget(null); -// tree.accept(envChainer); -// } -// -//// System.out.println("ATTRIBSTAT: " + attr.attribStat(statementNode.get(), envChainer.get())); -//// if (statementNode.get() instanceof JCVariableDecl) { -//// System.out.println("Force-tribbing expr"); -//// JCExpression init = ((JCVariableDecl)statementNode.get()).init; -//// System.out.println("ATTRIBEXPR: " + attr.attribExpr(init, envChainer.get(), Type.noType)); -//// System.out.println("TYPE: " + ((JCVariableDecl)statementNode.get()).init.type); -//// } -// } finally { -// logDisabler.enableLoggers(); -// } -// } -// -// public void resolveExpr(JCExpression expr) { -// logDisabler.disableLoggers(); -// try { -// attr.attribExpr(expr, envChainer.get(), Type.noType); -// } finally { -// logDisabler.enableLoggers(); -// } -// } - public static class TypeNotConvertibleException extends Exception { public TypeNotConvertibleException(String msg) { super(msg); } } + public static Type ifTypeIsIterableToComponent(Type type, JavacAST ast) { + Types types = Types.instance(ast.getContext()); + Symtab syms = Symtab.instance(ast.getContext()); + Type boundType = types.upperBound(type); + Type elemTypeIfArray = types.elemtype(boundType); + if (elemTypeIfArray != null) return elemTypeIfArray; + + Type base = types.asSuper(boundType, syms.iterableType.tsym); + if (base == null) return syms.objectType; + + List iterableParams = base.allparams(); + return iterableParams.isEmpty() ? syms.objectType : types.upperBound(iterableParams.head); + } + public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast) throws TypeNotConvertibleException { return typeToJCTree(type, maker, ast, false); } diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 7161a7c3..5da6fec0 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -64,7 +64,6 @@ public class HandleVal extends JavacASTAdapter { } local.mods.flags |= Flags.FINAL; - JCExpression oldVarType = local.vartype; local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); Type type; @@ -89,25 +88,26 @@ public class HandleVal extends JavacASTAdapter { JCExpression replacement; if (rhsOfEnhancedForLoop != null) { - localNode.addError("For now 'val' cannot be used in enhanced for loops. Fixing this is a high priority lombok issue though!"); - return; - // TODO: Fix enhanced for loops - then uncomment a bunch of lines in test/transform/resource/*/ValInFor.java + Type componentType = JavacResolution.ifTypeIsIterableToComponent(type, localNode.getAst()); + if (componentType == null) replacement = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); + else replacement = JavacResolution.typeToJCTree(componentType, localNode.getTreeMaker(), localNode.getAst()); + } else { + replacement = JavacResolution.typeToJCTree(type, localNode.getTreeMaker(), localNode.getAst()); } - replacement = JavacResolution.typeToJCTree(type, localNode.getTreeMaker(), localNode.getAst()); + if (replacement != null) { local.vartype = replacement; localNode.getAst().setChanged(); } - else local.vartype = oldVarType; + else local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst());; } catch (JavacResolution.TypeNotConvertibleException e) { localNode.addError("Cannot use 'val' here because initializer expression does not have a representable type: " + e.getMessage()); - local.vartype = oldVarType; + local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst());; } } catch (RuntimeException e) { - local.vartype = oldVarType; + local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst());; throw e; } } - // TODO search for val decls in either kind of for. } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index 0c9f1ccc..b5132ca4 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -267,6 +267,7 @@ public class EclipsePatcher extends Agent { private static void patchHandleVal(ScriptManager sm, boolean ecj) { final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration"; + final String FOREACHSTATEMENT_SIG = "org.eclipse.jdt.internal.compiler.ast.ForeachStatement"; 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"; @@ -278,6 +279,24 @@ public class EclipsePatcher extends Agent { .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_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()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .request(StackRequest.THIS, StackRequest.PARAM1) + .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) + .build()); + + sm.addScript(ScriptBuilder.replaceMethodCall() + .target(new MethodTarget(FOREACHSTATEMENT_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()); + if (!ecj) { sm.addScript(ScriptBuilder.addField() .fieldName("$initCopy") @@ -287,17 +306,25 @@ public class EclipsePatcher extends Agent { .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") .build()); + sm.addScript(ScriptBuilder.addField() + .fieldName("$iterableCopy") + .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.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void")) + .request(StackRequest.THIS) + .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "copyInitializationOfForEachIterable", "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 14687e32..32335ecc 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -38,14 +38,20 @@ 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.ForeachStatement; 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.ArrayBinding; +import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.parser.Parser; public class PatchFixes { @@ -140,18 +146,54 @@ public class PatchFixes { return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); } + private static Field astStackField, astPtrField; + + static { + try { + astStackField = Parser.class.getDeclaredField("astStack"); + astStackField.setAccessible(true); + astPtrField = Parser.class.getDeclaredField("astPtr"); + astPtrField.setAccessible(true); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + } + } + + public static void copyInitializationOfForEachIterable(Parser parser) { + ASTNode[] astStack; + int astPtr; + try { + astStack = (ASTNode[]) astStackField.get(parser); + astPtr = (Integer)astPtrField.get(parser); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + return; + } + + ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr]; + ASTNode init = foreachDecl.collection; + if (init == null) return; + if (foreachDecl.elementVariable != null && foreachDecl.elementVariable.type instanceof SingleTypeReference) { + SingleTypeReference ref = (SingleTypeReference) foreachDecl.elementVariable.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 { + if (iterableCopyField != null) iterableCopyField.set(foreachDecl.elementVariable, init); + } catch (Exception e) { + // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. + } + } + 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); + astStack = (ASTNode[]) astStackField.get(parser); + astPtr = (Integer)astPtrField.get(parser); } catch (Exception e) { - throw new RuntimeException(e); + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + return; } AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr]; if (!(variableDecl instanceof LocalDeclaration)) return; @@ -165,22 +207,69 @@ public class PatchFixes { try { if (initCopyField != null) initCopyField.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; + private static Field initCopyField, iterableCopyField; static { try { initCopyField = LocalDeclaration.class.getDeclaredField("$initCopy"); + iterableCopyField = LocalDeclaration.class.getDeclaredField("$iterableCopy"); } catch (Throwable t) { //ignore - no $initCopy exists when running in ecj. } } + public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { + if (forEach.elementVariable != null && forEach.elementVariable.type instanceof SingleTypeReference) { + char[] token = ((SingleTypeReference)forEach.elementVariable.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; + + TypeBinding component = getForEachComponentType(forEach.collection, scope); + TypeReference replacement = Eclipse.makeType(component, forEach.elementVariable.type, false); + + forEach.elementVariable.modifiers |= ClassFileConstants.AccFinal; + forEach.elementVariable.type = replacement != null ? replacement : + new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(forEach.elementVariable.type, 3)); + + return false; + } + + private static TypeBinding getForEachComponentType(Expression collection, BlockScope scope) { + if (collection != null) { + TypeBinding resolved = collection.resolveType(scope); + if (resolved.isArrayType()) { + resolved = ((ArrayBinding) resolved).elementsType(); + return resolved; + } else if (resolved instanceof ReferenceBinding) { + ReferenceBinding iterableType = ((ReferenceBinding)resolved).findSuperTypeOriginatingFrom(TypeIds.T_JavaLangIterable, false); + + TypeBinding[] arguments = null; + if (iterableType != null) switch (iterableType.kind()) { + case Binding.GENERIC_TYPE : // for (T t : Iterable) - in case used inside Iterable itself + arguments = iterableType.typeVariables(); + break; + case Binding.PARAMETERIZED_TYPE : // for(E e : Iterable) + arguments = ((ParameterizedTypeBinding)iterableType).arguments; + break; + } + + if (arguments != null && arguments.length == 1) { + return arguments[0]; + } + } + } + + return null; + } + public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) { + boolean decomponent = false; + if (local.type instanceof SingleTypeReference) { char[] token = ((SingleTypeReference)local.type).token; if (token == null || token.length != 3) return false; @@ -192,13 +281,23 @@ public class PatchFixes { try { init = (Expression) initCopyField.get(local); } catch (Exception e) { - throw new RuntimeException(e); + } + } + + if (init == null && iterableCopyField != null) { + try { + init = (Expression) iterableCopyField.get(local); + decomponent = true; + } catch (Exception e) { } } TypeReference replacement = null; + if (init != null && decomponent) { + } + if (init != null) { - TypeBinding resolved = init.resolveType(scope); + TypeBinding resolved = decomponent ? getForEachComponentType(init, scope) : init.resolveType(scope); if (resolved != null) { replacement = Eclipse.makeType(resolved, local.type, false); } -- cgit