8191842: JShell: Inferred type information is lost when assigning types to a \"var\"

For vars, upgrading all anonymous classes to member classes; stripping intersection types from fields before writing.

Reviewed-by: rfield
This commit is contained in:
Jan Lahoda 2018-01-19 17:11:52 +01:00
parent 1de9d061ec
commit e332323760
12 changed files with 917 additions and 181 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2018, 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
@ -36,6 +36,7 @@ import javax.lang.model.element.Modifier;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodTree;
@ -43,6 +44,7 @@ import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.Pretty;
import java.io.IOException;
@ -52,6 +54,8 @@ import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription.VariableDesc;
import jdk.jshell.Key.ErroneousKey;
import jdk.jshell.Key.MethodKey;
import jdk.jshell.Key.TypeDeclKey;
@ -60,6 +64,7 @@ import jdk.jshell.Snippet.SubKind;
import jdk.jshell.TaskFactory.AnalyzeTask;
import jdk.jshell.TaskFactory.BaseTask;
import jdk.jshell.TaskFactory.ParseTask;
import jdk.jshell.Util.Pair;
import jdk.jshell.Wrap.CompoundWrap;
import jdk.jshell.Wrap.Range;
import jdk.jshell.Snippet.Status;
@ -74,6 +79,7 @@ import jdk.jshell.spi.ExecutionControl.UserException;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static java.util.Collections.singletonList;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
import static jdk.jshell.Util.DOIT_METHOD_NAME;
import static jdk.jshell.Util.PREFIX_PATTERN;
@ -98,6 +104,11 @@ class Eval {
private int varNumber = 0;
/* The number of anonymous innerclasses seen so far. Used to generate unique
* names of these classes.
*/
private int anonCount = 0;
private final JShell state;
// The set of names of methods on Object
@ -203,7 +214,7 @@ class Eval {
case VARIABLE:
return processVariables(userSource, units, compileSourceInt, pt);
case EXPRESSION_STATEMENT:
return processExpression(userSource, compileSourceInt);
return processExpression(userSource, unitTree, compileSourceInt, pt);
case CLASS:
return processClass(userSource, unitTree, compileSourceInt, SubKind.CLASS_SUBKIND, pt);
case ENUM:
@ -285,15 +296,19 @@ class Eval {
String name = vt.getName().toString();
String typeName;
String fullTypeName;
String displayType;
boolean hasEnhancedType = false;
TreeDependencyScanner tds = new TreeDependencyScanner();
Wrap typeWrap;
Wrap anonDeclareWrap = null;
Wrap winit = null;
boolean enhancedDesugaring = false;
Set<String> anonymousClasses = Collections.emptySet();
StringBuilder sbBrackets = new StringBuilder();
Tree baseType = vt.getType();
if (baseType != null) {
tds.scan(baseType); // Not dependent on initializer
fullTypeName = typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
fullTypeName = displayType = typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
while (baseType instanceof ArrayTypeTree) {
//TODO handle annotations too
baseType = ((ArrayTypeTree) baseType).getType();
@ -311,83 +326,27 @@ class Eval {
Range rinit = dis.treeToRange(init);
String initCode = rinit.part(compileSource);
ExpressionInfo ei =
ExpressionToTypeInfo.localVariableTypeForInitializer(initCode, state);
typeName = ei == null ? "java.lang.Object" : ei.typeName;
fullTypeName = ei == null ? "java.lang.Object" : ei.fullTypeName;
if (ei != null && init.getKind() == Tree.Kind.NEW_CLASS &&
((NewClassTree) init).getClassBody() != null) {
NewClassTree nct = (NewClassTree) init;
StringBuilder constructor = new StringBuilder();
constructor.append(fullTypeName).append("(");
String sep = "";
if (ei.enclosingInstanceType != null) {
constructor.append(ei.enclosingInstanceType);
constructor.append(" encl");
sep = ", ";
}
int idx = 0;
for (String type : ei.parameterTypes) {
constructor.append(sep);
constructor.append(type);
constructor.append(" ");
constructor.append("arg" + idx++);
sep = ", ";
}
if (ei.enclosingInstanceType != null) {
constructor.append(") { encl.super (");
} else {
constructor.append(") { super (");
}
sep = "";
for (int i = 0; i < idx; i++) {
constructor.append(sep);
constructor.append("arg" + i++);
sep = ", ";
}
constructor.append("); }");
List<? extends Tree> members = nct.getClassBody().getMembers();
Range bodyRange = dis.treeListToRange(members);
Wrap bodyWrap;
ExpressionToTypeInfo.localVariableTypeForInitializer(initCode, state, false);
if (ei != null) {
typeName = ei.declareTypeName;
fullTypeName = ei.fullTypeName;
displayType = ei.displayTypeName;
if (bodyRange != null) {
bodyWrap = Wrap.rangeWrap(compileSource, bodyRange);
} else {
bodyWrap = Wrap.simpleWrap(" ");
}
hasEnhancedType = !typeName.equals(fullTypeName);
Range argRange = dis.treeListToRange(nct.getArguments());
Wrap argWrap;
enhancedDesugaring = !ei.isPrimitiveType;
if (argRange != null) {
argWrap = Wrap.rangeWrap(compileSource, argRange);
} else {
argWrap = Wrap.simpleWrap(" ");
}
if (ei.enclosingInstanceType != null) {
Range enclosingRanges =
dis.treeToRange(nct.getEnclosingExpression());
Wrap enclosingWrap = Wrap.rangeWrap(compileSource, enclosingRanges);
argWrap = argRange != null ? new CompoundWrap(enclosingWrap,
Wrap.simpleWrap(","),
argWrap)
: enclosingWrap;
}
Wrap hwrap = Wrap.simpleWrap("public static class " + fullTypeName +
(ei.isClass ? " extends " : " implements ") +
typeName + " { " + constructor);
anonDeclareWrap = new CompoundWrap(hwrap, bodyWrap, Wrap.simpleWrap("}"));
winit = new CompoundWrap("new " + fullTypeName + "(", argWrap, ")");
String superType = typeName;
typeName = fullTypeName;
fullTypeName = ei.isClass ? "<anonymous class extending " + superType + ">"
: "<anonymous class implementing " + superType + ">";
Pair<Wrap, Wrap> anonymous2Member =
anonymous2Member(ei, compileSource, rinit, dis, init);
anonDeclareWrap = anonymous2Member.first;
winit = anonymous2Member.second;
anonymousClasses = ei.anonymousClasses.stream().map(ad -> ad.declareTypeName).collect(Collectors.toSet());
} else {
displayType = fullTypeName = typeName = "java.lang.Object";
}
tds.scan(init);
} else {
fullTypeName = typeName = "java.lang.Object";
displayType = fullTypeName = typeName = "java.lang.Object";
}
typeWrap = Wrap.identityWrap(typeName);
}
@ -411,17 +370,193 @@ class Eval {
int nameEnd = nameStart + name.length();
Range rname = new Range(nameStart, nameEnd);
Wrap guts = Wrap.varWrap(compileSource, typeWrap, sbBrackets.toString(), rname,
winit, anonDeclareWrap);
DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
winit, enhancedDesugaring, anonDeclareWrap);
DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
name, subkind, fullTypeName,
name, subkind, displayType, hasEnhancedType ? fullTypeName : null, anonymousClasses,
tds.declareReferences(), modDiag);
snippets.add(snip);
}
return snippets;
}
private List<Snippet> processExpression(String userSource, String compileSource) {
/**Convert anonymous classes in "init" to member classes, based
* on the additional information from ExpressionInfo.anonymousClasses.
*
* This means:
* -if the code in the anonymous class captures any variables from the
* enclosing context, create fields for them
* -creating an explicit constructor that:
* --if the new class expression has a base/enclosing expression, make it an
* explicit constructor parameter "encl" and use "encl.super" when invoking
* the supertype constructor
* --if the (used) supertype constructor has any parameters, declare them
* as explicit parameters of the constructor, and pass them to the super
* constructor
* --if the code in the anonymous class captures any variables from the
* enclosing context, make them an explicit paramters of the constructor
* and assign to respective fields.
* --if there are any explicit fields with initializers in the anonymous class,
* move the initializers at the end of the constructor (after the captured fields
* are assigned, so that the initializers of these fields can use them).
* -from the captured variables fields, constructor, and existing members
* (with cleared field initializers), create an explicit class that extends or
* implements the supertype of the anonymous class.
*
* This method returns two wraps: the first contains the class declarations for the
* converted classes, the first one should be used instead of "init" in the variable
* declaration.
*/
private Pair<Wrap, Wrap> anonymous2Member(ExpressionInfo ei,
String compileSource,
Range rinit,
TreeDissector dis,
Tree init) {
List<Wrap> anonymousDeclarations = new ArrayList<>();
List<Wrap> partitionedInit = new ArrayList<>();
int lastPos = rinit.begin;
com.sun.tools.javac.util.List<NewClassTree> toConvert =
ExpressionToTypeInfo.listAnonymousClassesToConvert(init);
com.sun.tools.javac.util.List<AnonymousDescription> descriptions =
ei.anonymousClasses;
while (toConvert.nonEmpty() && descriptions.nonEmpty()) {
NewClassTree node = toConvert.head;
AnonymousDescription ad = descriptions.head;
toConvert = toConvert.tail;
descriptions = descriptions.tail;
List<Object> classBodyParts = new ArrayList<>();
//declarations of the captured variables:
for (VariableDesc vd : ad.capturedVariables) {
classBodyParts.add(vd.type + " " + vd.name + ";\n");
}
List<Object> constructorParts = new ArrayList<>();
constructorParts.add(ad.declareTypeName + "(");
String sep = "";
//add the parameter for the base/enclosing expression, if any:
if (ad.enclosingInstanceType != null) {
constructorParts.add(ad.enclosingInstanceType + " encl");
sep = ", ";
}
int idx = 0;
//add parameters of the super constructor, if any:
for (String type : ad.parameterTypes) {
constructorParts.add(sep);
constructorParts.add(type + " " + "arg" + idx++);
sep = ", ";
}
//add parameters for the captured variables:
for (VariableDesc vd : ad.capturedVariables) {
constructorParts.add(sep);
constructorParts.add(vd.type + " " + "cap$" + vd.name);
sep = ", ";
}
//construct super constructor call:
if (ad.enclosingInstanceType != null) {
//if there's an enclosing instance, call super on it:
constructorParts.add(") { encl.super (");
} else {
constructorParts.add(") { super (");
}
sep = "";
for (int i = 0; i < idx; i++) {
constructorParts.add(sep);
constructorParts.add("arg" + i);
sep = ", ";
}
constructorParts.add(");");
//initialize the captured variables:
for (VariableDesc vd : ad.capturedVariables) {
constructorParts.add("this." + vd.name + " = " + "cap$" + vd.name + ";\n");
}
List<? extends Tree> members =
node.getClassBody().getMembers();
for (Tree member : members) {
if (member.getKind() == Tree.Kind.VARIABLE) {
VariableTree vt = (VariableTree) member;
if (vt.getInitializer() != null) {
//for variables with initializer, explicitly move the initializer
//to the constructor after the captured variables as assigned
//(the initializers would otherwise run too early):
Range wholeVar = dis.treeToRange(vt);
int name = ((JCTree) vt).pos;
classBodyParts.add(new CompoundWrap(Wrap.rangeWrap(compileSource,
new Range(wholeVar.begin, name)),
vt.getName().toString(),
";\n"));
constructorParts.add(Wrap.rangeWrap(compileSource,
new Range(name, wholeVar.end)));
continue;
}
}
classBodyParts.add(Wrap.rangeWrap(compileSource,
dis.treeToRange(member)));
}
constructorParts.add("}");
//construct the member class:
classBodyParts.add(new CompoundWrap(constructorParts.toArray()));
Wrap classBodyWrap = new CompoundWrap(classBodyParts.toArray());
anonymousDeclarations.add(new CompoundWrap("public static class ", ad.declareTypeName,
(ad.isClass ? " extends " : " implements "),
ad.superTypeName, " { ", classBodyWrap, "}"));
//change the new class expression to use the newly created member type:
Range argRange = dis.treeListToRange(node.getArguments());
Wrap argWrap;
if (argRange != null) {
argWrap = Wrap.rangeWrap(compileSource, argRange);
} else {
argWrap = Wrap.simpleWrap(" ");
}
if (ad.enclosingInstanceType != null) {
//if there's an enclosing expression, set it as the first parameter:
Range enclosingRanges =
dis.treeToRange(node.getEnclosingExpression());
Wrap enclosingWrap = Wrap.rangeWrap(compileSource, enclosingRanges);
argWrap = argRange != null ? new CompoundWrap(enclosingWrap,
Wrap.simpleWrap(","),
argWrap)
: enclosingWrap;
}
Range current = dis.treeToRange(node);
String capturedArgs;
if (!ad.capturedVariables.isEmpty()) {
capturedArgs = (ad.parameterTypes.isEmpty() ? "" : ", ") +
ad.capturedVariables.stream()
.map(vd -> vd.name)
.collect(Collectors.joining(","));
} else {
capturedArgs = "";
}
if (lastPos < current.begin)
partitionedInit.add(Wrap.rangeWrap(compileSource,
new Range(lastPos, current.begin)));
partitionedInit.add(new CompoundWrap("new " + ad.declareTypeName + "(",
argWrap,
capturedArgs,
")"));
lastPos = current.end;
}
if (lastPos < rinit.end)
partitionedInit.add(Wrap.rangeWrap(compileSource, new Range(lastPos, rinit.end)));
return new Pair<>(new CompoundWrap(anonymousDeclarations.toArray()),
new CompoundWrap(partitionedInit.toArray()));
}
private List<Snippet> processExpression(String userSource, Tree tree, String compileSource, ParseTask pt) {
ExpressionStatementTree expr = (ExpressionStatementTree) tree;
String name = null;
ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(compileSource, state);
ExpressionTree assignVar;
@ -453,10 +588,31 @@ class Eval {
name = "$" + ++varNumber;
}
}
guts = Wrap.tempVarWrap(compileSource, ei.accessibleTypeName, name);
TreeDissector dis = TreeDissector.createByFirstClass(pt);
ExpressionInfo varEI =
ExpressionToTypeInfo.localVariableTypeForInitializer(compileSource, state, true);
String declareTypeName;
String fullTypeName;
String displayTypeName;
Set<String> anonymousClasses;
if (varEI != null) {
declareTypeName = varEI.declareTypeName;
fullTypeName = varEI.fullTypeName;
displayTypeName = varEI.displayTypeName;
Pair<Wrap, Wrap> anonymous2Member =
anonymous2Member(varEI, compileSource, new Range(0, compileSource.length()), dis, expr.getExpression());
guts = Wrap.tempVarWrap(anonymous2Member.second.wrapped(), declareTypeName, name, anonymous2Member.first);
anonymousClasses = varEI.anonymousClasses.stream().map(ad -> ad.declareTypeName).collect(Collectors.toSet());
} else {
declareTypeName = ei.accessibleTypeName;
displayTypeName = fullTypeName = typeName;
guts = Wrap.tempVarWrap(compileSource, declareTypeName, name, null);
anonymousClasses = Collections.emptySet();
}
Collection<String> declareReferences = null; //TODO
snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, typeName, declareReferences, null);
name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, displayTypeName, fullTypeName, anonymousClasses, declareReferences, null);
} else {
guts = Wrap.methodReturnWrap(compileSource);
snip = new ExpressionSnippet(state.keyMap.keyForExpression(name, typeName), userSource, guts,
@ -1059,4 +1215,7 @@ class Eval {
: new DiagList(new ModifierDiagnostic(list, fatal));
}
String computeDeclareName(TypeSymbol ts) {
return Util.JSHELL_ANONYMOUS + "$" + Long.toUnsignedString(anonCount++);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2018, 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
@ -25,12 +25,21 @@
package jdk.jshell;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.VariableElement;
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.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
@ -39,13 +48,21 @@ import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription;
import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription.VariableDesc;
import jdk.jshell.TaskFactory.AnalyzeTask;
import jdk.jshell.TypePrinter.AnonymousTypeKind;
/**
* Compute information about an expression string, particularly its type name.
@ -57,26 +74,75 @@ class ExpressionToTypeInfo {
final AnalyzeTask at;
final CompilationUnitTree cu;
final JShell state;
final boolean computeEnhancedInfo;
final boolean enhancedTypesAccessible;
final Symtab syms;
final Types types;
final Map<TypeSymbol, String> anon2Name = new HashMap<>();
private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state) {
private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state,
boolean computeEnhancedInfo, boolean enhancedTypesAccessible) {
this.at = at;
this.cu = cu;
this.state = state;
this.computeEnhancedInfo = computeEnhancedInfo;
this.enhancedTypesAccessible = enhancedTypesAccessible;
this.syms = Symtab.instance(at.context);
this.types = Types.instance(at.context);
}
public static class ExpressionInfo {
ExpressionTree tree;
boolean isPrimitiveType;
String typeName;
String accessibleTypeName;
/* In result of localVariableTypeForInitializer, the type that should be used
* as a declaration type of the field. This does not include intersection types,
* but does contain references to anonymous types converted to member types.
*/
String declareTypeName;
/* In result of localVariableTypeForInitializer, the apparent/infered type of
* the variable. This includes intersection types, and references to anonymous
* types converted to member types.
*/
String fullTypeName;
List<String> parameterTypes;
String enclosingInstanceType;
boolean isClass;
/* In result of localVariableTypeForInitializer, the human readable type of
* the variable. This includes intersection types, and human readable descriptions
* of anonymous types.
*/
String displayTypeName;
boolean isNonVoid;
/* In result of localVariableTypeForInitializer, description of important anonymous
* classes.
*/
List<AnonymousDescription> anonymousClasses = List.nil();
/* A description of an anonymous class. */
static class AnonymousDescription {
/* Parameter types of the invoked super constructor.*/
List<String> parameterTypes;
/* Type of the base/enclosing expression, if any.*/
String enclosingInstanceType;
/* The denotable name of the supertype.*/
String superTypeName;
/* The human-readable name of this class.*/
String declareTypeName;
/* If the supertype of this anonymous is a class. */
boolean isClass;
/* Variables captured by this anonymous class*/
List<VariableDesc> capturedVariables;
static class VariableDesc {
String type;
String name;
public VariableDesc(String type, String name) {
this.type = type;
this.name = name;
}
}
}
}
// return mechanism and other general structure from TreePath.getPath()
@ -173,7 +239,7 @@ class ExpressionToTypeInfo {
if (at.hasErrors() || cu == null) {
return null;
}
return new ExpressionToTypeInfo(at, cu, state).typeOfExpression();
return new ExpressionToTypeInfo(at, cu, state, false, false).typeOfExpression();
});
} catch (Exception ex) {
return null;
@ -187,7 +253,7 @@ class ExpressionToTypeInfo {
* @param state a JShell instance
* @return type information
*/
public static ExpressionInfo localVariableTypeForInitializer(String code, JShell state) {
public static ExpressionInfo localVariableTypeForInitializer(String code, JShell state, boolean onlyAccessible) {
if (code == null || code.isEmpty()) {
return null;
}
@ -198,13 +264,37 @@ class ExpressionToTypeInfo {
if (at.hasErrors() || cu == null) {
return null;
}
return new ExpressionToTypeInfo(at, cu, state).typeOfExpression();
return new ExpressionToTypeInfo(at, cu, state, true, onlyAccessible)
.typeOfExpression();
});
} catch (Exception ex) {
return null;
}
}
/**List (in a stable order) all NewClassTree instances under {@code from} that should be
* converted to member classes
*
* @param from tree to inspect
* @return NewClassTree instances that should be converted to member classes
*/
public static List<NewClassTree> listAnonymousClassesToConvert(Tree from) {
ListBuffer<NewClassTree> classes = new ListBuffer<>();
new TreeScanner<Void, Void>() {
@Override
public Void visitNewClass(NewClassTree node, Void p) {
if (node.getClassBody() != null) {
classes.append(node);
return null;
}
return super.visitNewClass(node, p);
}
}.scan(from, null);
return classes.toList();
}
private ExpressionInfo typeOfExpression() {
return treeToInfo(findExpressionPath());
}
@ -256,23 +346,31 @@ class ExpressionToTypeInfo {
* @return the type, if it is accessible, otherwise a superclass or
* interface which is
*/
private Type findAccessibleSupertype(Type type) {
private List<Type> findAccessibleSupertypes(Type type) {
List<Type> accessible = List.nil();
Type accessibleSuper = syms.objectType;
// Iterate up the superclasses, see if any are accessible
for (Type sup = type; !types.isSameType(sup, syms.objectType); sup = supertype(sup)) {
if (isAccessible(sup)) {
return sup;
accessible = accessible.prepend(sup);
accessibleSuper = sup;
break;
}
}
// Failing superclasses, look through superclasses for accessible interfaces
for (Type sup = type; !types.isSameType(sup, syms.objectType); sup = supertype(sup)) {
// then look through superclasses for accessible interfaces
for (Type sup = type; !types.isSameType(sup, accessibleSuper); sup = supertype(sup)) {
for (Type itf : types.interfaces(sup)) {
if (isAccessible(itf)) {
return itf;
accessible = accessible.prepend(itf);
}
}
}
// Punt, return Object which is the supertype of everything
return syms.objectType;
if (accessible.isEmpty()) {
// Punt, use Object which is the supertype of everything
accessible = accessible.prepend(syms.objectType);
}
return accessible.reverse();
}
private ExpressionInfo treeToInfo(TreePath tp) {
@ -298,48 +396,125 @@ class ExpressionToTypeInfo {
break;
default: {
ei.isNonVoid = true;
ei.typeName = varTypeName(type, false);
ei.accessibleTypeName = varTypeName(findAccessibleSupertype(type), false);
ei.fullTypeName = varTypeName(type, true);
ei.isPrimitiveType = type.isPrimitive();
ei.typeName = varTypeName(type, false, AnonymousTypeKind.SUPER);
List<Type> accessibleTypes = findAccessibleSupertypes(type);
ei.accessibleTypeName =
varTypeName(accessibleTypes.head, false, AnonymousTypeKind.SUPER);
if (computeEnhancedInfo) {
Type accessibleType = accessibleTypes.size() == 1 ? accessibleTypes.head
: types.makeIntersectionType(accessibleTypes);
ei.declareTypeName =
varTypeName(accessibleType, false, AnonymousTypeKind.DECLARE);
ei.fullTypeName =
varTypeName(enhancedTypesAccessible ? accessibleType : type,
true, AnonymousTypeKind.DECLARE);
ei.displayTypeName =
varTypeName(type, true, AnonymousTypeKind.DISPLAY);
}
break;
}
}
}
if (tree.getKind() == Tree.Kind.VARIABLE) {
if (tree.getKind() == Tree.Kind.VARIABLE && computeEnhancedInfo) {
Tree init = ((VariableTree) tree).getInitializer();
if (init.getKind() == Tree.Kind.NEW_CLASS &&
((NewClassTree) init).getClassBody() != null) {
NewClassTree nct = (NewClassTree) init;
ClassTree clazz = nct.getClassBody();
MethodTree constructor = (MethodTree) clazz.getMembers().get(0);
ExpressionStatementTree superCallStatement =
(ExpressionStatementTree) constructor.getBody().getStatements().get(0);
for (NewClassTree node : listAnonymousClassesToConvert(init)) {
Set<VariableElement> captured = capturedVariables(at,
tp.getCompilationUnit(),
node);
JCClassDecl clazz = (JCClassDecl) node.getClassBody();
MethodInvocationTree superCall =
(MethodInvocationTree) superCallStatement.getExpression();
TreePath superCallPath =
at.trees().getPath(tp.getCompilationUnit(), superCall.getMethodSelect());
clazz.getMembers()
.stream()
.map(TreeInfo::firstConstructorCall)
.findAny()
.get();
TreePath superCallPath
= at.trees().
getPath(tp.getCompilationUnit(), superCall.
getMethodSelect());
Type constrType = pathToType(superCallPath);
ei.parameterTypes = constrType.getParameterTypes()
.stream()
.map(t -> varTypeName(t, false))
.collect(List.collector());
if (nct.getEnclosingExpression() != null) {
TreePath enclPath = new TreePath(tp, nct.getEnclosingExpression());
ei.enclosingInstanceType = varTypeName(pathToType(enclPath), false);
AnonymousDescription desc = new AnonymousDescription();
desc.parameterTypes = constrType.getParameterTypes().
stream().
map(t -> varTypeName(t, false, AnonymousTypeKind.DECLARE)).
collect(List.collector());
if (node.getEnclosingExpression() != null) {
TreePath enclPath = new TreePath(tp,
node.getEnclosingExpression());
desc.enclosingInstanceType = varTypeName(pathToType(enclPath),
false,
AnonymousTypeKind.DECLARE);
}
ei.isClass = at.task.getTypes().directSupertypes(type).size() == 1;
TreePath currentPath = at.trees()
.getPath(tp.getCompilationUnit(),
node);
Type nodeType = pathToType(currentPath, node);
desc.superTypeName = varTypeName(nodeType,
false,
AnonymousTypeKind.SUPER);
desc.declareTypeName = varTypeName(nodeType,
true, AnonymousTypeKind.DECLARE);
desc.capturedVariables =
captured.stream()
.map(ve -> new VariableDesc(varTypeName((Type) ve.asType(),
false,
AnonymousTypeKind.DECLARE),
ve.getSimpleName().toString()))
.collect(List.collector());
desc.isClass = at.task.getTypes().directSupertypes(nodeType).size() == 1;
ei.anonymousClasses = ei.anonymousClasses.prepend(desc);
}
ei.anonymousClasses = ei.anonymousClasses.reverse();
}
return ei;
}
}
return null;
}
//where:
private static Set<VariableElement> capturedVariables(AnalyzeTask at,
CompilationUnitTree topLevel,
Tree tree) {
Set<VariableElement> capturedVars = new HashSet<>();
new TreeScanner<Void, Void>() {
Set<VariableElement> declaredLocalVars = new HashSet<>();
@Override
public Void visitVariable(VariableTree node, Void p) {
TreePath currentPath = at.trees()
.getPath(topLevel, node);
declaredLocalVars.add((VariableElement) at.trees().getElement(currentPath));
return super.visitVariable(node, p);
}
private String varTypeName(Type type, boolean printIntersectionTypes) {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
TreePath currentPath = at.trees()
.getPath(topLevel, node);
Element el = at.trees().getElement(currentPath);
if (el != null &&
LOCAL_VARIABLES.contains(el.getKind()) &&
!declaredLocalVars.contains(el)) {
capturedVars.add((VariableElement) el);
}
return super.visitIdentifier(node, p);
}
}.scan(tree, null);
return capturedVars;
}
private static final Set<ElementKind> LOCAL_VARIABLES =
EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.LOCAL_VARIABLE,
ElementKind.PARAMETER, ElementKind.RESOURCE_VARIABLE);
private String varTypeName(Type type, boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
try {
Function<TypeSymbol, String> anonymousClass2DeclareName =
cs -> anon2Name.computeIfAbsent(cs, state.eval::computeDeclareName);
TypePrinter tp = new TypePrinter(at.messages(),
state.maps::fullClassNameAndPackageToClass, printIntersectionTypes);
state.maps::fullClassNameAndPackageToClass, anonymousClass2DeclareName,
printIntersectionTypes, anonymousTypesKind);
List<Type> captures = types.captures(type);
String res = tp.toString(types.upward(type, captures));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2018, 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
@ -61,24 +61,33 @@ import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
import java.lang.Runtime.Version;
import java.nio.CharBuffer;
import java.util.function.BiFunction;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.tools.javac.api.JavacTaskPool;
import com.sun.tools.javac.code.ClassFinder;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
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.Resolve;
import com.sun.tools.javac.parser.Parser;
import com.sun.tools.javac.parser.ParserFactory;
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.JCTypeCast;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.util.Context.Factory;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Log.DiscardDiagnosticHandler;
import com.sun.tools.javac.util.Names;
@ -197,6 +206,7 @@ class TaskFactory {
compilationUnits, task -> {
JavacTaskImpl jti = (JavacTaskImpl) task;
Context context = jti.getContext();
DisableAccessibilityResolve.preRegister(context);
jti.addTaskListener(new TaskListenerImpl(context, state));
try {
return worker.withTask(creator.apply(jti, diagnostics));
@ -578,54 +588,111 @@ class TaskFactory {
}
}
/**The variable types inferred for "var"s may be non-denotable.
* jshell desugars these variables into fields, and fields must have
* a denotable type. So these fields are declared with some simpler denotable
* type, and the listener here enhances the types of the fields to be the full
* inferred types. This is mainly when the inferred type contains:
* -intersection types (e.g. <Z extends Runnable&CharSequence> Z get() {...} var z = get();)
* -types that are inaccessible at the given place
*
* This type enhancement does not need to do anything about anonymous classes, as these
* are desugared into member classes.
*/
private static final class TaskListenerImpl implements TaskListener {
private final Context context;
private final JShell state;
/* Keep the original (declaration) types of the fields that were enhanced.
* The declaration types need to be put back before writing the fields
* into classfiles.*/
private final Map<VarSymbol, Type> var2OriginalType = new HashMap<>();
public TaskListenerImpl(Context context, JShell state) {
this.context = context;
this.state = state;
}
@Override
public void started(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.GENERATE)
return ;
//clear enhanced types in fields we are about to write to the classfiles:
for (Tree clazz : e.getCompilationUnit().getTypeDecls()) {
ClassTree ct = (ClassTree) clazz;
for (Tree member : ct.getMembers()) {
if (member.getKind() != Tree.Kind.VARIABLE)
continue;
VarSymbol vsym = ((JCVariableDecl) member).sym;
Type original = var2OriginalType.remove(vsym);
if (original != null) {
vsym.type = original;
}
}
}
}
private boolean variablesSet = false;
@Override
public void finished(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ENTER)
if (e.getKind() != TaskEvent.Kind.ENTER || variablesSet)
return ;
state.maps
.snippetList()
.stream()
.filter(s -> s.status() == Status.VALID)
.filter(s -> s.kind() == Snippet.Kind.VAR)
.filter(s -> s.subKind() == Snippet.SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND)
.forEach(s -> setVariableType((JCCompilationUnit) e.getCompilationUnit(), (VarSnippet) s));
.filter(s -> s.subKind() == Snippet.SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND ||
s.subKind() == Snippet.SubKind.TEMP_VAR_EXPRESSION_SUBKIND)
.forEach(s -> setVariableType((VarSnippet) s));
variablesSet = true;
}
private void setVariableType(JCCompilationUnit root, VarSnippet s) {
/* If the snippet contain enhanced types, enhance the type of
* the variable from snippet s to be the enhanced type.
*/
private void setVariableType(VarSnippet s) {
String typeName = s.fullTypeName;
if (typeName == null)
return ;
Symtab syms = Symtab.instance(context);
Names names = Names.instance(context);
Log log = Log.instance(context);
ParserFactory parserFactory = ParserFactory.instance(context);
Attr attr = Attr.instance(context);
Enter enter = Enter.instance(context);
DisableAccessibilityResolve rs = (DisableAccessibilityResolve) Resolve.instance(context);
//find the variable:
ClassSymbol clazz = syms.getClass(syms.unnamedModule, names.fromString(s.classFullName()));
if (clazz == null || !clazz.isCompleted())
return;
VarSymbol field = (VarSymbol) clazz.members().findFirst(names.fromString(s.name()), sym -> sym.kind == Kinds.Kind.VAR);
if (field != null) {
if (field != null && !var2OriginalType.containsKey(field)) {
//if it was not enhanced yet:
//ignore any errors:
JavaFileObject prev = log.useSource(null);
DiscardDiagnosticHandler h = new DiscardDiagnosticHandler(log);
try {
String typeName = s.typeName();
//parse the type as a cast, i.e. "(<typeName>) x". This is to support
//intersection types:
CharBuffer buf = CharBuffer.wrap(("(" + typeName +")x\u0000").toCharArray(), 0, typeName.length() + 3);
Parser parser = parserFactory.newParser(buf, false, false, false);
JCExpression expr = parser.parseExpression();
if (expr.hasTag(Tag.TYPECAST)) {
//if parsed OK, attribute and set the type:
var2OriginalType.put(field, field.type);
JCTypeCast tree = (JCTypeCast) expr;
if (tree.clazz.hasTag(Tag.TYPEINTERSECTION)) {
rs.runWithoutAccessChecks(() -> {
field.type = attr.attribType(tree.clazz,
((JCClassDecl) root.getTypeDecls().head).sym);
}
enter.getEnvs().iterator().next().enclClass.sym);
});
}
} finally {
log.popDiagnosticHandler(h);
@ -635,4 +702,48 @@ class TaskFactory {
}
}
private static final class DisableAccessibilityResolve extends Resolve {
public static void preRegister(Context context) {
if (context.get(Marker.class) == null) {
context.put(resolveKey, ((Factory<Resolve>) c -> new DisableAccessibilityResolve(c)));
context.put(Marker.class, new Marker());
}
}
private boolean noAccessChecks;
public DisableAccessibilityResolve(Context context) {
super(context);
}
/**Run the given Runnable with all access checks disabled.
*
* @param r Runnnable to run
*/
public void runWithoutAccessChecks(Runnable r) {
boolean prevNoAccessCheckes = noAccessChecks;
try {
noAccessChecks = true;
r.run();
} finally {
noAccessChecks = prevNoAccessCheckes;
}
}
@Override
public boolean isAccessible(Env<AttrContext> env, TypeSymbol c, boolean checkInner) {
if (noAccessChecks) return true;
return super.isAccessible(env, c, checkInner);
}
@Override
public boolean isAccessible(Env<AttrContext> env, Type site, Symbol sym, boolean checkInner) {
if (noAccessChecks) return true;
return super.isAccessible(env, site, sym, checkInner);
}
private static final class Marker {}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2018, 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
@ -48,6 +48,7 @@ import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.lang.model.type.TypeMirror;
import jdk.jshell.TypePrinter.AnonymousTypeKind;
import jdk.jshell.Util.Pair;
/**
@ -227,7 +228,7 @@ class TreeDissector {
Type typeImpl = (Type) type;
try {
TypePrinter tp = new TypePrinter(at.messages(),
state.maps::fullClassNameAndPackageToClass, true);
state.maps::fullClassNameAndPackageToClass, true, AnonymousTypeKind.DISPLAY);
return tp.toString(typeImpl);
} catch (Exception ex) {
return null;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -30,14 +30,17 @@ import com.sun.tools.javac.code.Printer;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.Type.IntersectionClassType;
import com.sun.tools.javac.util.JavacMessages;
import java.util.Locale;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Print types in source form.
*/
@ -47,14 +50,44 @@ class TypePrinter extends Printer {
private final JavacMessages messages;
private final BinaryOperator<String> fullClassNameAndPackageToClass;
private final boolean printEnhancedTypes;
private final Function<TypeSymbol, String> anonymousToName;
private final boolean printIntersectionTypes;
private final AnonymousTypeKind anonymousTypesKind;
/**Create a TypePrinter.
*
* @param messages javac's messages
* @param fullClassNameAndPackageToClass convertor to convert full class names to
* simple class names.
* @param printIntersectionTypes whether intersection types should be printed
* @param anonymousTypesKind how the anonymous types should be printed
*/
TypePrinter(JavacMessages messages,
BinaryOperator<String> fullClassNameAndPackageToClass,
boolean printEnhancedTypes) {
boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
this(messages, fullClassNameAndPackageToClass, cs -> cs.flatName().toString(),
printIntersectionTypes, anonymousTypesKind);
}
/**Create a TypePrinter.
*
* @param messages javac's messages
* @param fullClassNameAndPackageToClass convertor to convert full class names to
* simple class names.
* @param anonymousToName convertor from anonymous classes to name that should be printed
* if anonymousTypesKind == AnonymousTypeKind.DECLARE
* @param printIntersectionTypes whether intersection types should be printed
* @param anonymousTypesKind how the anonymous types should be printed
*/
TypePrinter(JavacMessages messages,
BinaryOperator<String> fullClassNameAndPackageToClass,
Function<TypeSymbol, String> anonymousToName,
boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
this.messages = messages;
this.fullClassNameAndPackageToClass = fullClassNameAndPackageToClass;
this.printEnhancedTypes = printEnhancedTypes;
this.anonymousToName = anonymousToName;
this.printIntersectionTypes = printIntersectionTypes;
this.anonymousTypesKind = anonymousTypesKind;
}
String toString(Type t) {
@ -96,9 +129,9 @@ class TypePrinter extends Printer {
*/
@Override
protected String className(ClassType t, boolean longform, Locale locale) {
Symbol sym = t.tsym;
TypeSymbol sym = t.tsym;
if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) {
if (printEnhancedTypes) {
if (printIntersectionTypes) {
return ((IntersectionClassType) t).getExplicitComponents()
.stream()
.map(i -> visit(i, locale))
@ -107,18 +140,26 @@ class TypePrinter extends Printer {
return OBJECT;
}
} else if (sym.name.length() == 0) {
if (printEnhancedTypes) {
return t.tsym.flatName().toString().substring(t.tsym.outermostClass().flatName().length());
if (anonymousTypesKind == AnonymousTypeKind.DECLARE) {
return anonymousToName.apply(sym);
}
// Anonymous
String s;
boolean isClass;
ClassType norm = (ClassType) t.tsym.type;
if (norm == null) {
s = OBJECT;
isClass = true;
} else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
s = visit(norm.interfaces_field.head, locale);
isClass = false;
} else {
s = visit(norm.supertype_field, locale);
isClass = true;
}
if (anonymousTypesKind == AnonymousTypeKind.DISPLAY) {
s = isClass ? "<anonymous class extending " + s + ">"
: "<anonymous class implementing " + s + ">";
}
return s;
} else if (longform) {
@ -152,4 +193,14 @@ class TypePrinter extends Printer {
: s.fullname.toString();
}
/** Specifies how the anonymous classes should be handled. */
public enum AnonymousTypeKind {
/* The anonymous class is printed as the name of its supertype. */
SUPER,
/* The anonymous class is printed as converted by the anonymousToName
* convertor. */
DECLARE,
/* The anonymous class is printed in a human readable form. */
DISPLAY;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2018, 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
@ -55,6 +55,11 @@ class Util {
*/
static final String DOIT_METHOD_NAME = "do_it$";
/**
* The prefix for all anonymous classes upgraded to member classes.
*/
static final String JSHELL_ANONYMOUS = "$JShell$anonymous$";
/**
* A pattern matching the full or simple class name of a wrapper class.
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -26,6 +26,8 @@
package jdk.jshell;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import jdk.jshell.Key.VarKey;
/**
@ -41,15 +43,30 @@ import jdk.jshell.Key.VarKey;
*/
public class VarSnippet extends DeclarationSnippet {
/**A human readable type of the variable. May include intersection types
* and human readable description of anonymous classes.
*/
final String typeName;
/**The full type inferred for "var" variables. May include intersection types
* and inaccessible types. {@literal null} if enhancing the type is not necessary.
*/
final String fullTypeName;
/**The anonymous class declared in the initializer of the "var" variable.
* These are automatically statically imported when the field is imported.
*/
final Set<String> anonymousClasses;
VarSnippet(VarKey key, String userSource, Wrap guts,
String name, SubKind subkind, String typeName,
Collection<String> declareReferences,
String name, SubKind subkind, String typeName, String fullTypeName,
Set<String> anonymousClasses, Collection<String> declareReferences,
DiagList syntheticDiags) {
super(key, userSource, guts, name, subkind, null, declareReferences,
null, syntheticDiags);
this.typeName = typeName;
this.fullTypeName = fullTypeName;
this.anonymousClasses = anonymousClasses;
}
/**
@ -59,4 +76,13 @@ public class VarSnippet extends DeclarationSnippet {
public String typeName() {
return typeName;
}
@Override
String importLine(JShell state) {
return "import static " + classFullName() + "." + name() + ";\n" +
anonymousClasses.stream()
.map(c -> "import static " + classFullName() + "." + c + ";\n")
.collect(Collectors.joining());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -25,7 +25,9 @@
package jdk.jshell;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static java.util.stream.Collectors.joining;
import static jdk.jshell.Util.DOIT_METHOD_NAME;
@ -66,37 +68,76 @@ abstract class Wrap implements GeneralWrap {
return "\n" + indent(n);
}
/**
/**Create a stub of a compilable representation of a variable snippet.
* The variable is always represented by a field. If the variable
* in the snippet has an initializer, the field is initialized by
* calling the DOIT_METHOD_NAME method.
*
* @param in
* @param rname
* @param rinit Initializer or null
* @param rdecl Type name and name
* @return
* In some cases, the real inferred type of the variable may be non-denotable
* (e.g. intersection types). The declared type of the field must always
* be denotable (i.e. such that it can be written into the classfile), but
* if the real type is potentially non-denotable, the {@code enhanced} parameter
* must be true.
*
* @param source the snippet's masked source code
* @param wtype variable's denotable type suitable for field declaration
* @param brackets any [] that should be appended to the type
* @param rname range in source that denotes the name of the
* @param winit Initializer or null
* @param enhanced if the real inferred type of the variable is potentially
* non-denotable, this must be true
* @return a Wrap that declares the given variable, potentially with
* an initialization method
*/
public static Wrap varWrap(String source, Wrap wtype, String brackets,
Range rname, Wrap winit, Wrap anonDeclareWrap) {
Range rname, Wrap winit, boolean enhanced,
Wrap anonDeclareWrap) {
RangeWrap wname = new RangeWrap(source, rname);
Wrap wVarDecl = new VarDeclareWrap(wtype, brackets, wname);
List<Object> components = new ArrayList<>();
components.add(new VarDeclareWrap(wtype, brackets, wname));
Wrap wmeth;
if (winit == null) {
wmeth = new CompoundWrap(new NoWrap(" "), " return null;\n");
} else {
// int x = y
// int x_ = y; return x = x_;
// decl + "_ = " + init ; + "return " + name + "=" + name + "_ ;"
wmeth = new CompoundWrap(
wtype, brackets + " ", wname, "_ =\n ", winit, semi(winit),
" return ", wname, " = ", wname, "_;\n"
);
// int x = y
if (enhanced) {
// private static <Z> Z do_itAux() {
// wtype x_ = y;
// @SuppressWarnings("unchecked")
// Z x__ = (Z) x_;
// return x__;
// }
// in do_it method:
//return do_itAux();
Wrap waux = new CompoundWrap(
" private static <Z> Z ", DOIT_METHOD_NAME + "Aux", "() throws Throwable {\n",
wtype, brackets + " ", wname, "_ =\n ", winit, semi(winit),
" @SuppressWarnings(\"unchecked\") Z ", wname, "__ = (Z)", wname, "_;\n",
" return ", wname, "__;\n",
"}"
);
components.add(waux);
wmeth = new CompoundWrap(
" return ", wname, " = ", DOIT_METHOD_NAME + "Aux", "();\n"
);
} else {
// int x_ = y; return x = x_;
// decl + "_ = " + init ; + "return " + name + "= " + name + "_ ;"
wmeth = new CompoundWrap(
wtype, brackets + " ", wname, "_ =\n ", winit, semi(winit),
" return ", wname, " = ", wname, "_;\n"
);
}
}
Wrap wInitMeth = new DoitMethodWrap(wmeth);
return anonDeclareWrap != null ? new CompoundWrap(wVarDecl, wInitMeth, anonDeclareWrap)
: new CompoundWrap(wVarDecl, wInitMeth);
components.add(new DoitMethodWrap(wmeth));
if (anonDeclareWrap != null) {
components.add(anonDeclareWrap);
}
return new CompoundWrap(components.toArray());
}
public static Wrap tempVarWrap(String source, String typename, String name) {
public static Wrap tempVarWrap(String source, String typename, String name, Wrap anonDeclareWrap) {
RangeWrap winit = new NoWrap(source);
// y
// return $1 = y;
@ -105,7 +146,8 @@ abstract class Wrap implements GeneralWrap {
Wrap wInitMeth = new DoitMethodWrap(wmeth);
String varDecl = " public static\n " + typename + " " + name + ";\n";
return new CompoundWrap(varDecl, wInitMeth);
return anonDeclareWrap != null ? new CompoundWrap(varDecl, wInitMeth, anonDeclareWrap)
: new CompoundWrap(varDecl, wInitMeth);
}
public static Wrap simpleWrap(String source) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2018, 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
@ -22,7 +22,7 @@
*/
/*
* @test 8190939
* @test 8190939 8191842
* @summary test expressions whose type is inaccessible
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -86,6 +86,8 @@ public class InaccessibleExpressionTest extends KullaTesting {
assertEval(list.name() + ".size()", "0");
VarSnippet one = varKey(assertEval("priv()", "One"));
assertEquals(one.typeName(), "priv.GetPriv.Count");
assertEval("var v = down();", "Packp");
assertDeclareFail("v.toString()", "compiler.err.not.def.access.class.intf.cant.access");
}
public void testInternal() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2018, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103 8165405 8173073 8173848 8174041 8173916 8174028 8174262 8174797 8177079 8180508 8177466 8172154 8192979
* @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103 8165405 8173073 8173848 8174041 8173916 8174028 8174262 8174797 8177079 8180508 8177466 8172154 8192979 8191842
* @summary Simple jshell tool tests
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -786,7 +786,11 @@ public class ToolSimpleTest extends ReplToolTesting {
(a) -> assertCommandOutputContains(a, "var r1 = new Object() {}", "r1"),
(a) -> assertCommandOutputContains(a, "/vars r1", "| <anonymous class extending Object> r1 = "),
(a) -> assertCommandOutputContains(a, "var r2 = new Runnable() { public void run() { } }", "r2"),
(a) -> assertCommandOutputContains(a, "/vars r2", "| <anonymous class implementing Runnable> r2 = ")
(a) -> assertCommandOutputContains(a, "/vars r2", "| <anonymous class implementing Runnable> r2 = "),
(a) -> assertCommandOutputContains(a, "import java.util.stream.*;", ""),
(a) -> assertCommandOutputContains(a, "var list = Stream.of(1, 2, 3).map(j -> new Object() { int i = j; }).collect(Collectors.toList());",
"list"),
(a) -> assertCommandOutputContains(a, "/vars list", "| List<<anonymous class extending Object>> list = ")
);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 8144903 8171981 8191802
* @bug 8144903 8171981 8191802 8191842
* @summary Tests for determining the type from the expression
* @build KullaTesting TestingInputStream
* @run testng TypeNameTest
@ -38,8 +38,12 @@ public class TypeNameTest extends KullaTesting {
private void assertType(String expr, String type) {
assertType(expr, type, type);
}
private void assertType(String expr, String type, String inferType) {
assertEquals(varKey(assertEval(expr)).typeName(), type);
assertInferredType(expr, type);
assertInferredType(expr, inferType);
}
public void testTypeInference() {
@ -58,7 +62,7 @@ public class TypeNameTest extends KullaTesting {
assertType("d.getS()", "D<?>");
assertType("null", "Object");
assertType("Class.forName( \"java.util.ArrayList\" )", "Class<?>");
assertType("new ArrayList<Boolean>() {}", "ArrayList<Boolean>");
assertType("new ArrayList<Boolean>() {}", "<anonymous class extending 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>");
@ -188,7 +192,7 @@ public class TypeNameTest extends KullaTesting {
assertType("arrayOf(99)[0]", "Integer");
assertEval("<Z> Z choose(Z z1, Z z2) { return z1; }");
assertType("choose(1, 1L);", "Object");
assertType("choose(1, 1L);", "Number&Comparable<? extends Number&Comparable<?>>", "Object");
}
public void testVariableTypeName() {
@ -215,7 +219,7 @@ public class TypeNameTest extends KullaTesting {
public void testAnonymousClassName() {
assertEval("class C {}");
assertType("new C();", "C");
assertType("new C() { int x; };", "C");
assertType("new C() { int x; };", "<anonymous class extending C>", "C");
}
public void testCapturedTypeName() {
@ -243,7 +247,7 @@ public class TypeNameTest extends KullaTesting {
assertType("test1.get()", "CharSequence");
assertEval("class Test2<X extends Number & CharSequence> { public X get() { return null; } }");
assertEval("Test2<?> test2 = new Test2<>();");
assertType("test2.get()", "Object");
assertType("test2.get()", "Number&CharSequence", "Object");
assertEval("class Test3<T> { T[][] get() { return null; } }");
assertEval("Test3<? extends String> test3 = new Test3<>();");
assertType("test3.get()", "String[][]");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -23,12 +23,15 @@
/*
* @test
* @bug 8144903 8177466
* @bug 8144903 8177466 8191842
* @summary Tests for EvaluationState.variables
* @build KullaTesting TestingInputStream ExpectedDiagnostic
* @library /tools/lib
* @build Compiler KullaTesting TestingInputStream ExpectedDiagnostic
* @run testng VariablesTest
*/
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import javax.tools.Diagnostic;
@ -37,6 +40,7 @@ import jdk.jshell.TypeDeclSnippet;
import jdk.jshell.VarSnippet;
import jdk.jshell.Snippet.SubKind;
import jdk.jshell.SnippetEvent;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static java.util.stream.Collectors.toList;
@ -362,5 +366,157 @@ public class VariablesTest extends KullaTesting {
assertEval("class O2 { public class Inner { public Inner(int i) { } public String test() { return \"good\"; } } }");
assertEval("var r5 = new O2().new Inner(1) { public String get() { return \"good\"; } };");
assertEval("r5.get()", "\"good\"");
assertEval("<Z> Z identity(Z z) { return z; }");
assertEval("var r6 = identity(new Object() { String s = \"good\"; });");
assertEval("r6.s", "\"good\"");
assertEval("interface I<B, C> { C get(B b); }");
assertEval("<A, B, C> C cascade(A a, I<A, B> c1, I<B, C> c2) { return c2.get(c1.get(a)); }");
assertEval("var r7 = cascade(\"good\", a -> new Object() { String s = a; }, b -> new java.util.ArrayList<String>(5) { String s = b.s; });");
assertEval("r7.s", "\"good\"");
assertEval("var r8 = cascade(\"good\", a -> new Object() { String s = a; public String getS() { return s; } }, b -> new java.util.ArrayList<String>(5) { String s = b.getS(); public String getS() { return s; } });");
assertEval("r8.getS()", "\"good\"");
assertEval("var r9 = new Object() { class T { class Inner { public String g() { return outer(); } } public String outer() { return \"good\"; } public String test() { return new Inner() {}.g(); } } public String test() { return new T().test(); } };");
assertEval("r9.test()", "\"good\"");
assertEval("var nested1 = new Object() { class N { public String get() { return \"good\"; } } };");
assertEval("nested1.new N().get()", "\"good\"");
assertEval("var nested2 = cascade(\"good\", a -> new Object() { abstract class G { abstract String g(); } G g = new G() { String g() { return a; } }; }, b -> new java.util.ArrayList<String>(5) { String s = b.g.g(); });");
assertEval("nested2.s", "\"good\"");
assertEval("<A, B> B convert(A a, I<A, B> c) { return c.get(a); }");
assertEval("var r10 = convert(\"good\", a -> new api.C(12) { public String val = \"\" + i + s + l + a; } );");
assertEval("r10.val", "\"12empty[empty]good\"");
assertEval("var r11 = convert(\"good\", a -> new api.C(\"a\") { public String val = \"\" + i + s + l + a; } );");
assertEval("r11.val", "\"3a[empty]good\"");
assertEval("import api.C;");
assertEval("var r12 = convert(\"good\", a -> new C(java.util.List.of(\"a\")) { public String val = \"\" + i + s + l + a; } );");
assertEval("r12.val", "\"4empty[a]good\"");
assertEval("var r13 = convert(\"good\", a -> new api.G<String>(java.util.List.of(\"b\")) { public String val = \"\" + l + a; } );");
assertEval("r13.val", "\"[b]good\"");
assertEval("var r14 = convert(\"good\", a -> new api.J<String>() { public java.util.List<String> get() { return java.util.List.of(a, \"c\"); } } );");
assertEval("r14.get()", "[good, c]");
assertEval("var r15a = new java.util.ArrayList<String>();");
assertEval("r15a.add(\"a\");");
assertEval("var r15b = r15a.get(0);");
assertEval("r15b", "\"a\"");
}
public void test8191842() {
assertEval("import java.util.stream.*;");
assertEval("var list = Stream.of(1, 2, 3).map(j -> new Object() { int i = j; }).collect(Collectors.toList());");
assertEval("list.stream().map(a -> String.valueOf(a.i)).collect(Collectors.joining(\", \"));", "\"1, 2, 3\"");
}
public void lvtiRecompileDependentsWithIntersectionTypes() {
assertEval("<Z extends Runnable & CharSequence> Z get1() { return null; }", added(VALID));
VarSnippet var = varKey(assertEval("var i1 = get1();", added(VALID)));
assertEval("import java.util.stream.*;", added(VALID),
ste(var, VALID, VALID, true, MAIN_SNIPPET));
assertEval("void t1() { i1.run(); i1.length(); }", added(VALID));
}
public void arrayInit() {
assertEval("int[] d = {1, 2, 3};");
}
public void testAnonymousVar() {
assertEval("new Object() { public String get() { return \"a\"; } }");
assertEval("$1.get()", "\"a\"");
}
public void testIntersectionVar() {
assertEval("<Z extends Runnable & CharSequence> Z get() { return null; }", added(VALID));
assertEval("get();", added(VALID));
assertEval("void t1() { $1.run(); $1.length(); }", added(VALID));
}
public void multipleCaptures() {
assertEval("class D { D(int foo, String bar) { this.foo = foo; this.bar = bar; } int foo; String bar; } ");
assertEval("var d = new D(34, \"hi\") { String z = foo + bar; };");
assertEval("d.z", "\"34hi\"");
}
public void multipleAnonymous() {
VarSnippet v1 = varKey(assertEval("new Object() { public int i = 42; public int i1 = i; public int m1() { return i1; } };"));
VarSnippet v2 = varKey(assertEval("new Object() { public int i = 42; public int i2 = i; public int m2() { return i2; } };"));
assertEval(v1.name() + ".i", "42");
assertEval(v1.name() + ".i1", "42");
assertEval(v1.name() + ".m1()", "42");
assertDeclareFail(v1.name() + ".i2",
new ExpectedDiagnostic("compiler.err.cant.resolve.location", 0, 5, 2,
-1, -1, Diagnostic.Kind.ERROR));
assertEval(v2.name() + ".i", "42");
assertEval(v2.name() + ".i2", "42");
assertEval(v2.name() + ".m2()", "42");
assertDeclareFail(v2.name() + ".i1",
new ExpectedDiagnostic("compiler.err.cant.resolve.location", 0, 5, 2,
-1, -1, Diagnostic.Kind.ERROR));
}
public void displayName() {
assertVarDisplayName("var v1 = 234;", "int");
assertVarDisplayName("var v2 = new int[] {234};", "int[]");
assertEval("<Z extends Runnable & CharSequence> Z get() { return null; }", added(VALID));
assertVarDisplayName("var v3 = get();", "CharSequence&Runnable");
assertVarDisplayName("var v4a = new java.util.ArrayList<String>();", "java.util.ArrayList<String>");
assertEval("v4a.add(\"a\");");
assertVarDisplayName("var v4b = v4a.get(0);", "String");
assertVarDisplayName("var v5 = new Object() { };", "<anonymous class extending Object>");
assertVarDisplayName("var v6 = new Runnable() { public void run() { } };", "<anonymous class implementing Runnable>");
}
private void assertVarDisplayName(String var, String typeName) {
assertEquals(varKey(assertEval(var)).typeName(), typeName);
}
@BeforeMethod
@Override
public void setUp() {
Path path = Paths.get("cp");
Compiler compiler = new Compiler();
compiler.compile(path,
"package api;\n" +
"\n" +
"import java.util.List;\n" +
"\n" +
"public class C {\n" +
" public int i;\n" +
" public String s;\n" +
" public List<String> l;\n" +
" public C(int i) {\n" +
" this.i = i;\n" +
" this.s = \"empty\";\n" +
" this.l = List.of(\"empty\");\n" +
" }\n" +
" public C(String s) {\n" +
" this.i = 3;\n" +
" this.s = s;\n" +
" this.l = List.of(\"empty\");\n" +
" }\n" +
" public C(List<String> l) {\n" +
" this.i = 4;\n" +
" this.s = \"empty\";\n" +
" this.l = l;\n" +
" }\n" +
"}\n",
"package api;\n" +
"\n" +
"import java.util.List;\n" +
"\n" +
"public class G<T> {\n" +
" public List<T> l;\n" +
" public G(List<T> l) {\n" +
" this.l = l;\n" +
" }\n" +
"}\n",
"package api;\n" +
"\n" +
"import java.util.List;\n" +
"\n" +
"public interface J<T> {\n" +
" public List<T> get();\n" +
"}\n");
String tpath = compiler.getPath(path).toString();
setUp(b -> b
.remoteVMOptions("--class-path", tpath)
.compilerOptions("--class-path", tpath));
}
}