8171981: JShell: Fails compilation: new Object().getClass().getSuperclass()

Reviewed-by: mcimadamore
This commit is contained in:
Robert Field 2017-01-09 18:04:16 -08:00
parent bee65032d4
commit cea33151ed
12 changed files with 707 additions and 191 deletions

View File

@ -68,6 +68,11 @@ public class InternalDebugControl {
*/
public static final int DBG_EVNT = 0b0010000;
/**
* Event debugging.
*/
public static final int DBG_WRAP = 0b0100000;
private static Map<JShell, Integer> debugMap = null;
/**

View File

@ -119,6 +119,7 @@ import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_WRAP;
import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_MATCHER;
/**
@ -1919,9 +1920,13 @@ public class JShellTool implements MessageHandler {
flags |= DBG_EVNT;
fluff("Event debugging on");
break;
case 'w':
flags |= DBG_WRAP;
fluff("Wrap debugging on");
break;
default:
hard("Unknown debugging option: %c", ch);
fluff("Use: 0 r g f c d");
fluff("Use: 0 r g f c d e w");
return false;
}
}

View File

@ -49,6 +49,7 @@ import java.io.StringWriter;
import java.io.Writer;
import java.util.LinkedHashSet;
import java.util.Set;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo;
import jdk.jshell.Key.ErroneousKey;
import jdk.jshell.Key.MethodKey;
import jdk.jshell.Key.TypeDeclKey;
@ -58,7 +59,6 @@ import jdk.jshell.TaskFactory.AnalyzeTask;
import jdk.jshell.TaskFactory.BaseTask;
import jdk.jshell.TaskFactory.CompileTask;
import jdk.jshell.TaskFactory.ParseTask;
import jdk.jshell.TreeDissector.ExpressionInfo;
import jdk.jshell.Wrap.Range;
import jdk.jshell.Snippet.Status;
import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
@ -296,7 +296,7 @@ class Eval {
private List<Snippet> processExpression(String userSource, String compileSource) {
String name = null;
ExpressionInfo ei = typeOfExpression(compileSource);
ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(compileSource, state);
ExpressionTree assignVar;
Wrap guts;
Snippet snip;
@ -499,16 +499,6 @@ class Eval {
return singletonList(snip);
}
private ExpressionInfo typeOfExpression(String expression) {
Wrap guts = Wrap.methodReturnWrap(expression);
TaskFactory.AnalyzeTask at = trialCompile(guts);
if (!at.hasErrors() && at.firstCuTree() != null) {
return TreeDissector.createByFirstClass(at)
.typeOfReturnStatement(at, state);
}
return null;
}
/**
* Should a temp var wrap the expression. TODO make this user configurable.
*

View File

@ -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;
}
}
}

View File

@ -105,7 +105,6 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@ -128,6 +127,7 @@ import javax.lang.model.util.Types;
import javax.tools.JavaFileManager.Location;
import javax.tools.StandardLocation;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
import static jdk.jshell.TreeDissector.printType;
@ -1430,47 +1430,17 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
@Override
public String analyzeType(String code, int cursor) {
code = code.substring(0, cursor);
CompletionInfo completionInfo = analyzeCompletion(code);
if (!completionInfo.completeness().isComplete())
return null;
if (completionInfo.completeness() == Completeness.COMPLETE_WITH_SEMI) {
code += ";";
}
OuterWrap codeWrap;
switch (guessKind(code)) {
case IMPORT: case METHOD: case CLASS: case ENUM:
case INTERFACE: case ANNOTATION_TYPE: case VARIABLE:
return null;
default:
codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
break;
}
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap);
SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.firstCuTree();
int pos = codeWrap.snippetIndexToWrapIndex(code.length());
TreePath tp = pathFor(topLevel, sp, pos);
while (ExpressionTree.class.isAssignableFrom(tp.getParentPath().getLeaf().getKind().asInterface()) &&
tp.getParentPath().getLeaf().getKind() != Kind.ERRONEOUS &&
tp.getParentPath().getParentPath() != null)
tp = tp.getParentPath();
TypeMirror type = at.trees().getTypeMirror(tp);
if (type == null)
return null;
switch (type.getKind()) {
case ERROR: case NONE: case OTHER:
case PACKAGE: case VOID:
return null; //not usable
case NULL:
type = at.getElements().getTypeElement("java.lang.Object").asType();
break;
}
return TreeDissector.printType(at, proc, type);
ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(code, proc);
return (ei == null || !ei.isNonVoid)
? null
: ei.typeName;
}
@Override

View File

@ -321,7 +321,7 @@ class TaskFactory {
final JavacTaskImpl task;
private DiagList diags = null;
private final SourceHandler<?> sourceHandler;
private final Context context = new Context();
final Context context = new Context();
private Types types;
private JavacMessages messages;
private Trees trees;

View File

@ -28,14 +28,11 @@ package jdk.jshell;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.MethodType;
@ -47,7 +44,6 @@ import jdk.jshell.TaskFactory.AnalyzeTask;
import jdk.jshell.Wrap.Range;
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;
import java.util.stream.Stream;
@ -61,16 +57,6 @@ import jdk.jshell.Util.Pair;
class TreeDissector {
private static final String OBJECT_TYPE = "Object";
static class ExpressionInfo {
boolean isNonVoid;
String typeName;
ExpressionTree tree;
String signature;
}
private final TaskFactory.BaseTask bt;
private final ClassTree targetClass;
private final CompilationUnitTree targetCompilationUnit;
@ -219,41 +205,6 @@ class TreeDissector {
return null;
}
ExpressionInfo typeOfReturnStatement(AnalyzeTask at, JShell state) {
ExpressionInfo ei = new ExpressionInfo();
Tree unitTree = firstStatement();
if (unitTree instanceof ReturnTree) {
ei.tree = ((ReturnTree) unitTree).getExpression();
if (ei.tree != null) {
TreePath viPath = trees().getPath(targetCompilationUnit, ei.tree);
if (viPath != null) {
TypeMirror tm = trees().getTypeMirror(viPath);
if (tm != null) {
ei.typeName = printType(at, state, tm);
switch (tm.getKind()) {
case VOID:
case NONE:
case ERROR:
case OTHER:
break;
case NULL:
ei.isNonVoid = true;
ei.typeName = OBJECT_TYPE;
break;
default: {
ei.isNonVoid = true;
break;
}
}
}
}
}
}
return ei;
}
String typeOfMethod(MethodSnippet msn) {
Tree unitTree = method(msn);
if (unitTree instanceof JCMethodDecl) {
@ -274,8 +225,13 @@ class TreeDissector {
public static String printType(AnalyzeTask at, JShell state, TypeMirror type) {
Type typeImpl = (Type) type;
TypePrinter tp = new TypePrinter(at.messages(), state.maps::fullClassNameAndPackageToClass, typeImpl);
return tp.visit(typeImpl, Locale.getDefault());
try {
TypePrinter tp = new TypePrinter(at.messages(),
state.maps::fullClassNameAndPackageToClass);
return tp.toString(typeImpl);
} catch (Exception ex) {
return null;
}
}
/**

View File

@ -22,7 +22,6 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jshell;
import static com.sun.tools.javac.code.Flags.COMPOUND;
@ -41,17 +40,21 @@ import java.util.function.BinaryOperator;
* Print types in source form.
*/
class TypePrinter extends Printer {
private static final String OBJECT = "Object";
private final JavacMessages messages;
private final BinaryOperator<String> fullClassNameAndPackageToClass;
private boolean useWildCard = false;
TypePrinter(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass, Type typeToPrint) {
TypePrinter(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass) {
this.messages = messages;
this.fullClassNameAndPackageToClass = fullClassNameAndPackageToClass;
}
String toString(Type t) {
return visit(t, Locale.getDefault());
}
@Override
protected String localize(Locale locale, String key, Object... args) {
return messages.getLocalizedString(locale, key, args);
@ -67,18 +70,6 @@ class TypePrinter extends Printer {
return visit(t.wildcard, locale);
}
@Override
public String visitWildcardType(Type.WildcardType wt, Locale locale) {
if (useWildCard) { // at TypeArgument(ex: List<? extends T>)
return super.visitWildcardType(wt, locale);
} else { // at TopLevelType(ex: ? extends List<T>, ? extends Number[][])
Type extendsBound = wt.getExtendsBound();
return extendsBound == null
? OBJECT
: visit(extendsBound, locale);
}
}
@Override
public String visitType(Type t, Locale locale) {
String s = (t.tsym == null || t.tsym.name == null)
@ -87,20 +78,9 @@ class TypePrinter extends Printer {
return s;
}
@Override
public String visitClassType(ClassType ct, Locale locale) {
boolean prevUseWildCard = useWildCard;
try {
useWildCard = true;
return super.visitClassType(ct, locale);
} finally {
useWildCard = prevUseWildCard;
}
}
/**
* Converts a class name into a (possibly localized) string. Anonymous
* inner classes get converted into a localized string.
* Converts a class name into a (possibly localized) string. Anonymous inner
* classes get converted into a localized string.
*
* @param t the type of the class whose name is to be rendered
* @param longform if set, the class' fullname is displayed - if unset the
@ -112,21 +92,13 @@ class TypePrinter extends Printer {
protected String className(ClassType t, boolean longform, Locale locale) {
Symbol sym = t.tsym;
if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) {
/***
StringBuilder s = new StringBuilder(visit(t.supertype_field, locale));
for (List<Type> is = t.interfaces_field; is.nonEmpty(); is = is.tail) {
s.append('&');
s.append(visit(is.head, locale));
}
return s.toString();
***/
return OBJECT;
} else if (sym.name.length() == 0) {
// Anonymous
String s;
ClassType norm = (ClassType) t.tsym.type;
if (norm == null) {
s = "object";
s = OBJECT;
} else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
s = visit(norm.interfaces_field.head, locale);
} else {
@ -160,7 +132,7 @@ class TypePrinter extends Printer {
@Override
public String visitPackageSymbol(PackageSymbol s, Locale locale) {
return s.isUnnamed()
? "" // Unnamed package
? "" // Unnamed package
: s.fullname.toString();
}

View File

@ -46,6 +46,7 @@ import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_WRAP;
import static jdk.jshell.Snippet.Status.OVERWRITTEN;
import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED;
import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
@ -180,6 +181,8 @@ final class Unit {
.collect(toList());
// Set the outer wrap for this snippet
si.setOuterWrap(state.outerMap.wrapInClass(except, plus, snippets, wraps));
state.debug(DBG_WRAP, "++setWrap() %s\n%s\n",
si, si.outerWrap().wrapped());
}
}

View File

@ -0,0 +1,267 @@
/*
* 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 java.util.HashSet;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.util.JavacMessages;
import java.util.Locale;
import java.util.Set;
import java.util.function.BinaryOperator;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type.CapturedType;
import com.sun.tools.javac.code.Type.TypeMapping;
import com.sun.tools.javac.code.Type.TypeVar;
import com.sun.tools.javac.code.Type.WildcardType;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.code.Types.SimpleVisitor;
import com.sun.tools.javac.util.List;
import static com.sun.tools.javac.code.BoundKind.EXTENDS;
import static com.sun.tools.javac.code.BoundKind.SUPER;
import static com.sun.tools.javac.code.BoundKind.UNBOUND;
import static com.sun.tools.javac.code.Type.ArrayType;
import static com.sun.tools.javac.code.TypeTag.BOT;
import static com.sun.tools.javac.code.TypeTag.WILDCARD;
/**
* Print variable types in source form.
* TypeProjection and CaptureScanner are copied from Types in the JEP-286
* Sandbox by Maurizio. The checks for Non-Denotable in TypePrinter are
* cribbed from denotableChecker of the same source.
*
* @author Maurizio Cimadamore
* @author Robert Field
*/
class VarTypePrinter extends TypePrinter {
private static final String WILD = "?";
private final Symtab syms;
private final Types types;
VarTypePrinter(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass,
Symtab syms, Types types) {
super(messages, fullClassNameAndPackageToClass);
this.syms = syms;
this.types = types;
}
@Override
String toString(Type t) {
return super.toString(upward(t));
}
@Override
public String visitTypeVar(TypeVar t, Locale locale) {
/* Any type variable mentioned in the inferred type must have been declared as a type parameter
(i.e cannot have been produced by inference (18.4))
*/
// and beyond that, there are no global type vars, so if there are any
// type variables left, they need to be eliminated
return WILD; // Non-denotable
}
@Override
public String visitCapturedType(CapturedType t, Locale locale) {
/* Any type variable mentioned in the inferred type must have been declared as a type parameter
(i.e cannot have been produced by capture conversion (5.1.10))
*/
return WILD; // Non-denotable
}
public Type upward(Type t) {
List<Type> captures = captures(t);
return upward(t, captures);
}
/************* Following from JEP-286 Types.java ***********/
public Type upward(Type t, List<Type> vars) {
return t.map(new TypeProjection(vars), true);
}
public List<Type> captures(Type t) {
CaptureScanner cs = new CaptureScanner();
Set<Type> captures = new HashSet<>();
cs.visit(t, captures);
return List.from(captures);
}
class CaptureScanner extends SimpleVisitor<Void, Set<Type>> {
@Override
public Void visitType(Type t, Set<Type> types) {
return null;
}
@Override
public Void visitClassType(ClassType t, Set<Type> seen) {
if (t.isCompound()) {
types.directSupertypes(t).forEach(s -> visit(s, seen));
} else {
t.allparams().forEach(ta -> visit(ta, seen));
}
return null;
}
@Override
public Void visitArrayType(ArrayType t, Set<Type> seen) {
return visit(t.elemtype, seen);
}
@Override
public Void visitWildcardType(WildcardType t, Set<Type> seen) {
visit(t.type, seen);
return null;
}
@Override
public Void visitTypeVar(TypeVar t, Set<Type> seen) {
if ((t.tsym.flags() & Flags.SYNTHETIC) != 0 && seen.add(t)) {
visit(t.getUpperBound(), seen);
}
return null;
}
@Override
public Void visitCapturedType(CapturedType t, Set<Type> seen) {
if (seen.add(t)) {
visit(t.getUpperBound(), seen);
visit(t.getLowerBound(), seen);
}
return null;
}
}
class TypeProjection extends TypeMapping<Boolean> {
List<Type> vars;
Set<Type> seen = new HashSet<>();
public TypeProjection(List<Type> vars) {
this.vars = vars;
}
@Override
public Type visitClassType(ClassType t, Boolean upward) {
if (upward && !t.isCompound() && t.tsym.name.isEmpty()) {
//lift anonymous class type to first supertype (class or interface)
return types.directSupertypes(t).last();
} else if (t.isCompound()) {
List<Type> components = types.directSupertypes(t);
List<Type> components1 = components.map(c -> c.map(this, upward));
if (components == components1) return t;
else return types.makeIntersectionType(components1);
} else {
Type outer = t.getEnclosingType();
Type outer1 = visit(outer, upward);
List<Type> typarams = t.getTypeArguments();
List<Type> typarams1 = typarams.stream()
.map(ta -> mapTypeArgument(ta, upward))
.collect(List.collector());
if (typarams1.stream().anyMatch(ta -> ta.hasTag(BOT))) {
//not defined
return syms.botType;
}
if (outer1 == outer && typarams1 == typarams) return t;
else return new ClassType(outer1, typarams1, t.tsym, t.getMetadata()) {
@Override
protected boolean needsStripping() {
return true;
}
};
}
}
protected Type makeWildcard(Type upper, Type lower) {
BoundKind bk;
Type bound;
if (upper.hasTag(BOT)) {
upper = syms.objectType;
}
boolean isUpperObject = types.isSameType(upper, syms.objectType);
if (!lower.hasTag(BOT) && isUpperObject) {
bound = lower;
bk = SUPER;
} else {
bound = upper;
bk = isUpperObject ? UNBOUND : EXTENDS;
}
return new WildcardType(bound, bk, syms.boundClass);
}
@Override
public Type visitTypeVar(TypeVar t, Boolean upward) {
if (vars.contains(t)) {
try {
if (seen.add(t)) {
return (upward ?
t.getUpperBound() :
(t.getLowerBound() == null) ?
syms.botType :
t.getLowerBound())
.map(this, upward);
} else {
//cycle
return syms.objectType;
}
} finally {
seen.remove(t);
}
} else {
return t;
}
}
@Override
public Type visitWildcardType(WildcardType wt, Boolean upward) {
if (upward) {
return wt.isExtendsBound() ?
wt.type.map(this, upward) :
syms.objectType;
} else {
return wt.isSuperBound() ?
wt.type.map(this, upward) :
syms.botType;
}
}
private Type mapTypeArgument(Type t, boolean upward) {
if (!t.containsAny(vars)) {
return t;
} else if (!t.hasTag(WILDCARD) && !upward) {
//not defined
return syms.botType;
} else {
Type upper = t.map(this, upward);
Type lower = t.map(this, !upward);
return makeWildcard(upper, lower);
}
}
}
}

View File

@ -23,81 +23,229 @@
/*
* @test
* @bug 8144903
* @bug 8144903 8171981
* @summary Tests for determining the type from the expression
* @build KullaTesting TestingInputStream
* @run testng TypeNameTest
*/
import jdk.jshell.Snippet;
import jdk.jshell.VarSnippet;
import org.testng.annotations.Test;
import static jdk.jshell.Snippet.Status.VALID;
import static org.testng.Assert.assertEquals;
@Test
public class TypeNameTest extends KullaTesting {
public void testReplClassName() {
assertEval("class C {}");
VarSnippet sn = (VarSnippet) varKey(assertEval("new C();"));
assertEquals(sn.typeName(), "C");
private void assertType(String expr, String type) {
assertEquals(varKey(assertEval(expr)).typeName(), type);
assertInferredType(expr, type);
}
public void testTypeInference() {
assertEval("import java.util.List;");
assertEval("import java.util.ArrayList;");
assertEval("import java.util.Arrays;");
assertType("new Object().getClass().getSuperclass() ", "Class<?>");
assertType("new ArrayList().getClass().getSuperclass()", "Class<?>");
assertType("new ArrayList().getClass()", "Class<? extends ArrayList>");
assertType("ArrayList.class", "Class<ArrayList>");
assertType("ArrayList.class.getSuperclass()", "Class<? super ArrayList>");
assertEval("class D<T extends CharSequence> { D<? super T> getS() { return null; } }");
assertEval("D<?> d = new D<String>();");
assertType("d.getS()", "D<? extends CharSequence>");
assertType("null", "Object");
assertType("Class.forName( \"java.util.ArrayList\" )", "Class<?>");
assertType("new ArrayList<Boolean>() {}", "ArrayList<Boolean>");
assertType("new ArrayList<String>().stream()", "java.util.stream.Stream<String>");
assertType("Arrays.asList( 1, 2, 3)", "List<Integer>");
assertType("new ArrayList().getClass().getClass()", "Class<? extends Class>");
assertEval("interface A {}");
assertEval("interface I {}");
assertEval("interface J extends A, I {}");
assertEval("interface K extends A, I {}");
assertEval("class P<T extends A & I> {}");
assertType("(P<?>) null", "P<? extends Object>");
}
public void testConditionals() {
assertEval("import java.util.List;");
assertEval("import java.util.ArrayList;");
assertEval("import java.util.Arrays;");
assertEval("CharSequence cs = \"hi\";");
assertEval("String st = \"low\";");
assertEval("boolean b;");
assertType("b? cs : st", "CharSequence");
assertEval("List<String> l1 = Arrays.asList(\"hi\");");
assertEval("List<? extends String> l2 = Arrays.asList(\"po\");");
assertType("b? l1.get(0) : l2.get(0)", "String");
assertEval("class X {}");
assertEval("class B extends X {}");
assertEval("class C extends X {}");
assertType("b? new B() : new C()", "X");
}
public void testJEP286NonDenotable() {
assertEval("import java.util.List;");
assertEval("import java.util.Arrays;");
assertEval("import java.util.Iterator;");
assertEval("List<? extends String> extString() { return Arrays.asList( \"hi\", \"low\" ); }");
assertEval("List<? super String> supString() { return Arrays.asList( \"hi\", \"low\" ); }");
assertEval("List<?> unbString() { return Arrays.asList( \"hi\", \"low\" ); }");
assertEval("List<? extends String>[] extStringArr() {" +
" @SuppressWarnings(\"unchecked\") " +
"List<? extends String>[] a = new List[1]; a[0] = Arrays.asList(\"hi\"); return a; }");
assertEval("List<? super String>[] supStringArr() {" +
" @SuppressWarnings(\"unchecked\") " +
"List<? super String>[] a = new List[1]; a[0] = Arrays.asList(\"hi\"); return a; }");
assertEval("List<?>[] unbStringArr() {" +
" @SuppressWarnings(\"unchecked\") " +
"List<?>[] a = new List[1]; a[0] = Arrays.asList(\"hi\"); return a; }");
assertEval("Iterable<? extends List<? extends String>> extStringIter() {" +
"return Arrays.asList( Arrays.asList( \"hi\" ) ); }");
assertEval("Iterable<? extends List<? super String>> supStringIter() {" +
"return Arrays.asList( Arrays.asList( \"hi\" ) ); }");
assertEval("Iterable<? extends List<?>> unbStringIter() {" +
"return Arrays.asList( Arrays.asList( \"hi\" ) ); }");
assertType("extString()", "List<? extends String>");
assertType("extString().get(0)", "String");
assertType("supString()", "List<? super String>");
assertType("supString().get(0)", "Object");
assertType("unbString()", "List<?>");
assertType("unbString().get(0)", "Object");
assertType("supStringArr()", "List<? super String>[]");
assertType("supStringArr()[0]", "List<? super String>");
assertType("supStringArr()[0].get(0)", "Object");
assertType("unbStringArr()", "List<?>[]");
assertType("unbStringArr()[0]", "List<?>");
assertType("unbStringArr()[0].get(0)", "Object");
assertType("extStringIter()", "Iterable<? extends List<? extends String>>");
assertType("extStringIter().iterator()", "Iterator<? extends List<? extends String>>");
assertType("extStringIter().iterator().next()", "List<? extends String>");
assertType("extStringIter().iterator().next().get(0)", "String");
assertType("supStringIter()", "Iterable<? extends List<? super String>>");
assertType("supStringIter().iterator()", "Iterator<? extends List<? super String>>");
assertType("supStringIter().iterator().next()", "List<? super String>");
assertType("supStringIter().iterator().next().get(0)", "Object");
assertType("unbStringIter()", "Iterable<? extends List<?>>");
assertType("unbStringIter().iterator()", "Iterator<? extends List<?>>");
assertType("unbStringIter().iterator().next()", "List<?>");
assertType("unbStringIter().iterator().next().get(0)", "Object");
}
public void testJEP286NonDenotable2() {
assertEval("import java.util.List;");
assertEval("import java.util.Arrays;");
assertEval("import java.lang.reflect.Array;");
assertEval("<Z extends Comparable<Z>> List<? extends Z> extFbound() {" +
"return Arrays.asList( (Z)null ); }");
assertEval("<Z extends Comparable<Z>> List<? super Z> supFbound() {" +
"return Arrays.asList( (Z)null ); }");
assertEval("<Z extends Comparable<Z>> List<? extends Z>[] extFboundArr() {" +
"@SuppressWarnings(\"unchecked\")" +
"List<? extends Z>[] a = new List[1]; a[0] = Arrays.asList( (Z)null ); return a; }");
assertEval("<Z extends Comparable<Z>> List<? super Z>[] supFboundArr() {" +
"@SuppressWarnings(\"unchecked\")" +
"List<? super Z>[] a = new List[1]; a[0] = Arrays.asList( (Z)null ); return a; }");
assertEval("<Z extends Comparable<Z>> Iterable<? extends List<? extends Z>> extFboundIter() {" +
"return Arrays.asList( Arrays.asList( (Z)null ) ); }");
assertEval("<Z extends Comparable<Z>> Iterable<? extends List<? super Z>> supFboundIter() {" +
"return Arrays.asList( Arrays.asList( (Z)null ) ); }");
assertEval("<Z> List<Z> listOf(Z z) { return Arrays.asList( z ); }");
assertEval("<Z> Z[] arrayOf(Z z) {" +
"@SuppressWarnings(\"unchecked\")" +
"final Z[] a = (Z[]) Array.newInstance(z.getClass(), 1); a[0] = z; return a; }");
assertType("extFbound()", "List<? extends Comparable<?>>");
assertType("extFbound().get(0)", "Comparable<?>");
assertType("supFbound()", "List<?>");
assertType("supFbound().get(0)", "Object");
assertType("extFboundArr()", "List<? extends Comparable<?>>[]");
assertType("extFboundArr()[0]", "List<? extends Comparable<?>>");
assertType("extFboundArr()[0].get(0)", "Comparable<?>");
assertType("supFboundArr()", "List<?>[]");
assertType("supFboundArr()[0]", "List<?>");
assertType("supFboundArr()[0].get(0)", "Object");
assertType("extFboundIter()", "Iterable<? extends List<? extends Comparable<?>>>");
assertType("extFboundIter().iterator()", "java.util.Iterator<? extends List<? extends Comparable<?>>>");
assertType("extFboundIter().iterator().next()", "List<? extends Comparable<?>>");
assertType("extFboundIter().iterator().next().get(0)", "Comparable<?>");
assertType("supFboundIter()", "Iterable<? extends List<?>>");
assertType("supFboundIter().iterator()", "java.util.Iterator<? extends List<?>>");
assertType("supFboundIter().iterator().next()", "List<?>");
assertType("supFboundIter().iterator().next().get(0)", "Object");
assertType("listOf(23)", "List<Integer>");
assertType("listOf(true)", "List<Boolean>");
assertType("listOf(true).get(0)", "Boolean");
assertType("arrayOf(99)", "Integer[]");
assertType("arrayOf(99)[0]", "Integer");
assertEval("<Z> Z choose(Z z1, Z z2) { return z1; }");
assertType("choose(1, 1L);", "Object");
}
public void testVariableTypeName() {
assertType("\"x\"", "String");
assertType("java.util.regex.Pattern.compile(\"x\")", "java.util.regex.Pattern");
assertEval("import java.util.regex.*;");
assertType("java.util.regex.Pattern.compile(\"x\")", "Pattern");
assertType("new java.util.ArrayList()", "java.util.ArrayList");
assertEval("import java.util.ArrayList;");
assertType("new java.util.ArrayList()", "ArrayList");
assertType("java.util.Locale.Category.FORMAT", "java.util.Locale.Category");
assertEval("import static java.util.Locale.Category;");
assertType("java.util.Locale.Category.FORMAT", "Category");
}
public void testReplNestedClassName() {
assertEval("class D { static class E {} }");
VarSnippet sn = (VarSnippet) varKey(assertEval("new D.E();"));
assertEquals(sn.typeName(), "D.E");
assertType("new D.E();", "D.E");
}
public void testAnonymousClassName() {
assertEval("class C {}");
VarSnippet sn = (VarSnippet) varKey(assertEval("new C() { int x; };"));
assertEquals(sn.typeName(), "C");
assertType("new C();", "C");
assertType("new C() { int x; };", "C");
}
public void testCapturedTypeName() {
VarSnippet sn = (VarSnippet) varKey(assertEval("\"\".getClass();"));
assertEquals(sn.typeName(), "Class<? extends String>");
}
public void testArrayTypeOfCapturedTypeName() {
VarSnippet sn = (VarSnippet) varKey(assertEval("\"\".getClass().getEnumConstants();"));
assertEquals(sn.typeName(), "String[]");
assertType("\"\".getClass();", "Class<? extends String>");
assertType("\"\".getClass().getEnumConstants();", "String[]");
}
public void testJavaLang() {
VarSnippet sn = (VarSnippet) varKey(assertEval("\"\";"));
assertEquals(sn.typeName(), "String");
assertType("\"\";", "String");
}
public void testNotOverEagerPackageEating() {
VarSnippet sn = (VarSnippet) varKey(assertEval("\"\".getClass().getDeclaredMethod(\"hashCode\");"));
assertEquals(sn.typeName(), "java.lang.reflect.Method");
assertType("\"\".getClass().getDeclaredMethod(\"hashCode\");", "java.lang.reflect.Method");
}
public void testBounds() {
assertEval("java.util.List<? extends String> list1 = java.util.Arrays.asList(\"\");");
VarSnippet sn1 = (VarSnippet) varKey(assertEval("list1.iterator().next()"));
assertEquals(sn1.typeName(), "String");
assertType("list1.iterator().next()", "String");
assertEval("java.util.List<? super String> list2 = java.util.Arrays.asList(\"\");");
VarSnippet sn2 = (VarSnippet) varKey(assertEval("list2.iterator().next()"));
assertEquals(sn2.typeName(), "Object");
assertType("list2.iterator().next()", "Object");
assertEval("java.util.List<?> list3 = java.util.Arrays.asList(\"\");");
VarSnippet sn3 = (VarSnippet) varKey(assertEval("list3.iterator().next()"));
assertEquals(sn3.typeName(), "Object");
assertType("list3.iterator().next()", "Object");
assertEval("class Test1<X extends CharSequence> { public X get() { return null; } }");
Snippet x = varKey(assertEval("Test1<?> test1 = new Test1<>();"));
VarSnippet sn4 = (VarSnippet) varKey(assertEval("test1.get()"));
assertEquals(sn4.typeName(), "Object");
assertEval("Test1<?> test1 = new Test1<>();");
assertType("test1.get()", "CharSequence");
assertEval("class Test2<X extends Number & CharSequence> { public X get() { return null; } }");
assertEval("Test2<?> test2 = new Test2<>();");
VarSnippet sn5 = (VarSnippet) varKey(assertEval("test2.get()"));
assertEquals(sn5.typeName(), "Object");
assertEval("class Test3<T> { T[][] get() { return null; } }", added(VALID));
assertType("test2.get()", "Object");
assertEval("class Test3<T> { T[][] get() { return null; } }");
assertEval("Test3<? extends String> test3 = new Test3<>();");
VarSnippet sn6 = (VarSnippet) varKey(assertEval("test3.get()"));
assertEquals(sn6.typeName(), "String[][]");
assertType("test3.get()", "String[][]");
}
}

View File

@ -337,20 +337,4 @@ public class VariablesTest extends KullaTesting {
assertEquals(unr.get(0), "class undefined");
assertVariables(variable("undefined", "d"));
}
public void variableTypeName() {
assertEquals(varKey(assertEval("\"x\"")).typeName(), "String");
assertEquals(varKey(assertEval("java.util.regex.Pattern.compile(\"x\")")).typeName(), "java.util.regex.Pattern");
assertEval("import java.util.regex.*;", added(VALID));
assertEquals(varKey(assertEval("java.util.regex.Pattern.compile(\"x\")")).typeName(), "Pattern");
assertEquals(varKey(assertEval("new java.util.ArrayList()")).typeName(), "java.util.ArrayList");
assertEval("import java.util.ArrayList;", added(VALID));
assertEquals(varKey(assertEval("new java.util.ArrayList()")).typeName(), "ArrayList");
assertEquals(varKey(assertEval("java.util.Locale.Category.FORMAT")).typeName(), "java.util.Locale.Category");
assertEval("import static java.util.Locale.Category;", added(VALID));
assertEquals(varKey(assertEval("java.util.Locale.Category.FORMAT")).typeName(), "Category");
}
}