src/jdk.jshell/share/classes/jdk/jshell/ExpressionToTypeInfo.java
changeset 47216 71c04702a3d5
parent 43134 006808ae5f6e
child 47268 48ec75306997
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/ExpressionToTypeInfo.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell;
+
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Types;
+import jdk.jshell.TaskFactory.AnalyzeTask;
+
+/**
+ * Compute information about an expression string, particularly its type name.
+ */
+class ExpressionToTypeInfo {
+
+    private static final String OBJECT_TYPE_NAME = "Object";
+
+    final AnalyzeTask at;
+    final CompilationUnitTree cu;
+    final JShell state;
+    final Symtab syms;
+    final Types types;
+
+    private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state) {
+        this.at = at;
+        this.cu = cu;
+        this.state = state;
+        this.syms = Symtab.instance(at.context);
+        this.types = Types.instance(at.context);
+    }
+
+    public static class ExpressionInfo {
+        ExpressionTree tree;
+        String typeName;
+        boolean isNonVoid;
+    }
+
+    // return mechanism and other general structure from TreePath.getPath()
+    private static class Result extends Error {
+
+        static final long serialVersionUID = -5942088234594905629L;
+        final TreePath expressionPath;
+
+        Result(TreePath path) {
+            this.expressionPath = path;
+        }
+    }
+
+    private static class PathFinder extends TreePathScanner<TreePath, Boolean> {
+
+        // Optimize out imports etc
+        @Override
+        public TreePath visitCompilationUnit(CompilationUnitTree node, Boolean isTargetContext) {
+            return scan(node.getTypeDecls(), isTargetContext);
+        }
+
+        // Only care about members
+        @Override
+        public TreePath visitClass(ClassTree node, Boolean isTargetContext) {
+            return scan(node.getMembers(), isTargetContext);
+        }
+
+        // Only want the doit method where the code is
+        @Override
+        public TreePath visitMethod(MethodTree node, Boolean isTargetContext) {
+            if (Util.isDoIt(node.getName())) {
+                return scan(node.getBody(), true);
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public TreePath visitReturn(ReturnTree node, Boolean isTargetContext) {
+            ExpressionTree tree = node.getExpression();
+            TreePath tp = new TreePath(getCurrentPath(), tree);
+            if (isTargetContext) {
+                throw new Result(tp);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private Type pathToType(TreePath tp) {
+        return (Type) at.trees().getTypeMirror(tp);
+    }
+
+    private Type pathToType(TreePath tp, Tree tree) {
+        if (tree instanceof ConditionalExpressionTree) {
+            // Conditionals always wind up as Object -- this corrects
+            ConditionalExpressionTree cet = (ConditionalExpressionTree) tree;
+            Type tmt = pathToType(new TreePath(tp, cet.getTrueExpression()));
+            Type tmf = pathToType(new TreePath(tp, cet.getFalseExpression()));
+            if (!tmt.isPrimitive() && !tmf.isPrimitive()) {
+                Type lub = types.lub(tmt, tmf);
+                // System.err.printf("cond ? %s : %s  --  lub = %s\n",
+                //             varTypeName(tmt), varTypeName(tmf), varTypeName(lub));
+                return lub;
+            }
+        }
+        return pathToType(tp);
+    }
+
+    /**
+     * Entry method: get expression info
+     * @param code the expression as a string
+     * @param state a JShell instance
+     * @return type information
+     */
+    public static ExpressionInfo expressionInfo(String code, JShell state) {
+        if (code == null || code.isEmpty()) {
+            return null;
+        }
+        try {
+            OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code));
+            AnalyzeTask at = state.taskFactory.new AnalyzeTask(codeWrap);
+            CompilationUnitTree cu = at.firstCuTree();
+            if (at.hasErrors() || cu == null) {
+                return null;
+            }
+            return new ExpressionToTypeInfo(at, cu, state).typeOfExpression();
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    private ExpressionInfo typeOfExpression() {
+        return treeToInfo(findExpressionPath());
+    }
+
+    private TreePath findExpressionPath() {
+        try {
+            new PathFinder().scan(new TreePath(cu), false);
+        } catch (Result result) {
+            return result.expressionPath;
+        }
+        return null;
+    }
+
+    private ExpressionInfo treeToInfo(TreePath tp) {
+        if (tp != null) {
+            Tree tree = tp.getLeaf();
+            if (tree instanceof ExpressionTree) {
+                ExpressionInfo ei = new ExpressionInfo();
+                ei.tree = (ExpressionTree) tree;
+                Type type = pathToType(tp, tree);
+                if (type != null) {
+                    switch (type.getKind()) {
+                        case VOID:
+                        case NONE:
+                        case ERROR:
+                        case OTHER:
+                            break;
+                        case NULL:
+                            ei.isNonVoid = true;
+                            ei.typeName = OBJECT_TYPE_NAME;
+                            break;
+                        default: {
+                            ei.isNonVoid = true;
+                            ei.typeName = varTypeName(type);
+                            if (ei.typeName == null) {
+                                ei.typeName = OBJECT_TYPE_NAME;
+                            }
+                            break;
+                        }
+                    }
+                }
+                return ei;
+            }
+        }
+        return null;
+    }
+
+    private String varTypeName(Type type) {
+        try {
+            TypePrinter tp = new VarTypePrinter(at.messages(),
+                    state.maps::fullClassNameAndPackageToClass, syms, types);
+            return tp.toString(type);
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+}