blob: f33c37133a6bd190067a14950b029d68e81a13c9 [file] [log] [blame]
/*
* 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;
}
}
}