1/* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.jshell; 27 28import com.sun.source.tree.ReturnTree; 29import com.sun.source.tree.ClassTree; 30import com.sun.source.tree.CompilationUnitTree; 31import com.sun.source.tree.ConditionalExpressionTree; 32import com.sun.source.tree.ExpressionTree; 33import com.sun.source.tree.MethodTree; 34import com.sun.source.tree.Tree; 35import com.sun.source.util.TreePath; 36import com.sun.source.util.TreePathScanner; 37import com.sun.tools.javac.code.Symtab; 38import com.sun.tools.javac.code.Type; 39import com.sun.tools.javac.code.Types; 40import jdk.jshell.TaskFactory.AnalyzeTask; 41 42/** 43 * Compute information about an expression string, particularly its type name. 44 */ 45class ExpressionToTypeInfo { 46 47 private static final String OBJECT_TYPE_NAME = "Object"; 48 49 final AnalyzeTask at; 50 final CompilationUnitTree cu; 51 final JShell state; 52 final Symtab syms; 53 final Types types; 54 55 private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state) { 56 this.at = at; 57 this.cu = cu; 58 this.state = state; 59 this.syms = Symtab.instance(at.context); 60 this.types = Types.instance(at.context); 61 } 62 63 public static class ExpressionInfo { 64 ExpressionTree tree; 65 String typeName; 66 boolean isNonVoid; 67 } 68 69 // return mechanism and other general structure from TreePath.getPath() 70 private static class Result extends Error { 71 72 static final long serialVersionUID = -5942088234594905629L; 73 final TreePath expressionPath; 74 75 Result(TreePath path) { 76 this.expressionPath = path; 77 } 78 } 79 80 private static class PathFinder extends TreePathScanner<TreePath, Boolean> { 81 82 // Optimize out imports etc 83 @Override 84 public TreePath visitCompilationUnit(CompilationUnitTree node, Boolean isTargetContext) { 85 return scan(node.getTypeDecls(), isTargetContext); 86 } 87 88 // Only care about members 89 @Override 90 public TreePath visitClass(ClassTree node, Boolean isTargetContext) { 91 return scan(node.getMembers(), isTargetContext); 92 } 93 94 // Only want the doit method where the code is 95 @Override 96 public TreePath visitMethod(MethodTree node, Boolean isTargetContext) { 97 if (Util.isDoIt(node.getName())) { 98 return scan(node.getBody(), true); 99 } else { 100 return null; 101 } 102 } 103 104 @Override 105 public TreePath visitReturn(ReturnTree node, Boolean isTargetContext) { 106 ExpressionTree tree = node.getExpression(); 107 TreePath tp = new TreePath(getCurrentPath(), tree); 108 if (isTargetContext) { 109 throw new Result(tp); 110 } else { 111 return null; 112 } 113 } 114 } 115 116 private Type pathToType(TreePath tp) { 117 return (Type) at.trees().getTypeMirror(tp); 118 } 119 120 private Type pathToType(TreePath tp, Tree tree) { 121 if (tree instanceof ConditionalExpressionTree) { 122 // Conditionals always wind up as Object -- this corrects 123 ConditionalExpressionTree cet = (ConditionalExpressionTree) tree; 124 Type tmt = pathToType(new TreePath(tp, cet.getTrueExpression())); 125 Type tmf = pathToType(new TreePath(tp, cet.getFalseExpression())); 126 if (!tmt.isPrimitive() && !tmf.isPrimitive()) { 127 Type lub = types.lub(tmt, tmf); 128 // System.err.printf("cond ? %s : %s -- lub = %s\n", 129 // varTypeName(tmt), varTypeName(tmf), varTypeName(lub)); 130 return lub; 131 } 132 } 133 return pathToType(tp); 134 } 135 136 /** 137 * Entry method: get expression info 138 * @param code the expression as a string 139 * @param state a JShell instance 140 * @return type information 141 */ 142 public static ExpressionInfo expressionInfo(String code, JShell state) { 143 if (code == null || code.isEmpty()) { 144 return null; 145 } 146 try { 147 OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code)); 148 AnalyzeTask at = state.taskFactory.new AnalyzeTask(codeWrap); 149 CompilationUnitTree cu = at.firstCuTree(); 150 if (at.hasErrors() || cu == null) { 151 return null; 152 } 153 return new ExpressionToTypeInfo(at, cu, state).typeOfExpression(); 154 } catch (Exception ex) { 155 return null; 156 } 157 } 158 159 private ExpressionInfo typeOfExpression() { 160 return treeToInfo(findExpressionPath()); 161 } 162 163 private TreePath findExpressionPath() { 164 try { 165 new PathFinder().scan(new TreePath(cu), false); 166 } catch (Result result) { 167 return result.expressionPath; 168 } 169 return null; 170 } 171 172 private ExpressionInfo treeToInfo(TreePath tp) { 173 if (tp != null) { 174 Tree tree = tp.getLeaf(); 175 if (tree instanceof ExpressionTree) { 176 ExpressionInfo ei = new ExpressionInfo(); 177 ei.tree = (ExpressionTree) tree; 178 Type type = pathToType(tp, tree); 179 if (type != null) { 180 switch (type.getKind()) { 181 case VOID: 182 case NONE: 183 case ERROR: 184 case OTHER: 185 break; 186 case NULL: 187 ei.isNonVoid = true; 188 ei.typeName = OBJECT_TYPE_NAME; 189 break; 190 default: { 191 ei.isNonVoid = true; 192 ei.typeName = varTypeName(type); 193 if (ei.typeName == null) { 194 ei.typeName = OBJECT_TYPE_NAME; 195 } 196 break; 197 } 198 } 199 } 200 return ei; 201 } 202 } 203 return null; 204 } 205 206 private String varTypeName(Type type) { 207 try { 208 TypePrinter tp = new VarTypePrinter(at.messages(), 209 state.maps::fullClassNameAndPackageToClass, syms, types); 210 return tp.toString(type); 211 } catch (Exception ex) { 212 return null; 213 } 214 } 215 216} 217