8010822: Intersection type cast for functional expressions does not follow spec EDR

Remove support for marker interfaces; redefine intersection type casts to be order-independent

Reviewed-by: jjg
This commit is contained in:
Maurizio Cimadamore 2013-04-08 15:59:29 +01:00
parent ea55015155
commit da9dd76b20
9 changed files with 160 additions and 125 deletions

View File

@ -908,6 +908,12 @@ public class Type implements PrimitiveType {
return interfaces_field.prepend(supertype_field);
}
public List<Type> getExplicitComponents() {
return allInterfaces ?
interfaces_field :
getComponents();
}
@Override
public TypeKind getKind() {
return TypeKind.INTERSECTION;

View File

@ -610,7 +610,7 @@ public class Types {
/**
* Scope filter used to skip methods that should be ignored (such as methods
* overridden by j.l.Object) during function interface conversion/marker interface checks
* overridden by j.l.Object) during function interface conversion interface check
*/
class DescriptorFilter implements Filter<Symbol> {
@ -629,64 +629,6 @@ public class Types {
}
};
// <editor-fold defaultstate="collapsed" desc="isMarker">
/**
* A cache that keeps track of marker interfaces
*/
class MarkerCache {
private WeakHashMap<TypeSymbol, Entry> _map = new WeakHashMap<TypeSymbol, Entry>();
class Entry {
final boolean isMarkerIntf;
final int prevMark;
public Entry(boolean isMarkerIntf,
int prevMark) {
this.isMarkerIntf = isMarkerIntf;
this.prevMark = prevMark;
}
boolean matches(int mark) {
return this.prevMark == mark;
}
}
boolean get(TypeSymbol origin) throws FunctionDescriptorLookupError {
Entry e = _map.get(origin);
CompoundScope members = membersClosure(origin.type, false);
if (e == null ||
!e.matches(members.getMark())) {
boolean isMarkerIntf = isMarkerInterfaceInternal(origin, members);
_map.put(origin, new Entry(isMarkerIntf, members.getMark()));
return isMarkerIntf;
}
else {
return e.isMarkerIntf;
}
}
/**
* Is given symbol a marker interface
*/
public boolean isMarkerInterfaceInternal(TypeSymbol origin, CompoundScope membersCache) throws FunctionDescriptorLookupError {
return !origin.isInterface() ?
false :
!membersCache.getElements(new DescriptorFilter(origin)).iterator().hasNext();
}
}
private MarkerCache markerCache = new MarkerCache();
/**
* Is given type a marker interface?
*/
public boolean isMarkerInterface(Type site) {
return markerCache.get(site.tsym);
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="isSubtype">
/**
* Is t an unchecked subtype of s?
@ -2625,15 +2567,15 @@ public class Types {
public List<MethodSymbol> interfaceCandidates(Type site, MethodSymbol ms) {
Filter<Symbol> filter = new MethodFilter(ms, site);
List<MethodSymbol> candidates = List.nil();
for (Symbol s : membersClosure(site, false).getElements(filter)) {
if (!site.tsym.isInterface() && !s.owner.isInterface()) {
return List.of((MethodSymbol)s);
} else if (!candidates.contains(s)) {
candidates = candidates.prepend((MethodSymbol)s);
for (Symbol s : membersClosure(site, false).getElements(filter)) {
if (!site.tsym.isInterface() && !s.owner.isInterface()) {
return List.of((MethodSymbol)s);
} else if (!candidates.contains(s)) {
candidates = candidates.prepend((MethodSymbol)s);
}
}
return prune(candidates);
}
return prune(candidates);
}
public List<MethodSymbol> prune(List<MethodSymbol> methods) {
ListBuffer<MethodSymbol> methodsMin = ListBuffer.lb();

View File

@ -2273,7 +2273,7 @@ public class Attr extends JCTree.Visitor {
Type lambdaType;
if (pt() != Type.recoveryType) {
target = checkIntersectionTarget(that, target, resultInfo.checkContext);
target = targetChecker.visit(target, that);
lambdaType = types.findDescriptorType(target);
chk.checkFunctionalInterface(that, target);
} else {
@ -2281,7 +2281,7 @@ public class Attr extends JCTree.Visitor {
lambdaType = fallbackDescriptorType(that);
}
setFunctionalInfo(that, pt(), lambdaType, resultInfo.checkContext.inferenceContext());
setFunctionalInfo(that, pt(), lambdaType, target, resultInfo.checkContext.inferenceContext());
if (lambdaType.hasTag(FORALL)) {
//lambda expression target desc cannot be a generic method
@ -2396,26 +2396,55 @@ public class Attr extends JCTree.Visitor {
}
}
}
private Type checkIntersectionTarget(DiagnosticPosition pos, Type pt, CheckContext checkContext) {
if (pt != Type.recoveryType && pt.isCompound()) {
IntersectionClassType ict = (IntersectionClassType)pt;
List<Type> bounds = ict.allInterfaces ?
ict.getComponents().tail :
ict.getComponents();
types.findDescriptorType(bounds.head); //propagate exception outwards!
for (Type bound : bounds.tail) {
if (!types.isMarkerInterface(bound)) {
checkContext.report(pos, diags.fragment("secondary.bound.must.be.marker.intf", bound));
}
}
//for now (translation doesn't support intersection types)
return bounds.head;
} else {
return pt;
}
}
//where
Types.MapVisitor<DiagnosticPosition> targetChecker = new Types.MapVisitor<DiagnosticPosition>() {
@Override
public Type visitClassType(ClassType t, DiagnosticPosition pos) {
return t.isCompound() ?
visitIntersectionClassType((IntersectionClassType)t, pos) : t;
}
public Type visitIntersectionClassType(IntersectionClassType ict, DiagnosticPosition pos) {
Symbol desc = types.findDescriptorSymbol(makeNotionalInterface(ict));
Type target = null;
for (Type bound : ict.getExplicitComponents()) {
TypeSymbol boundSym = bound.tsym;
if (types.isFunctionalInterface(boundSym) &&
types.findDescriptorSymbol(boundSym) == desc) {
target = bound;
} else if (!boundSym.isInterface() || (boundSym.flags() & ANNOTATION) != 0) {
//bound must be an interface
reportIntersectionError(pos, "not.an.intf.component", boundSym);
}
}
return target != null ?
target :
ict.getExplicitComponents().head; //error recovery
}
private TypeSymbol makeNotionalInterface(IntersectionClassType ict) {
ListBuffer<Type> targs = ListBuffer.lb();
ListBuffer<Type> supertypes = ListBuffer.lb();
for (Type i : ict.interfaces_field) {
if (i.isParameterized()) {
targs.appendList(i.tsym.type.allparams());
}
supertypes.append(i.tsym.type);
}
IntersectionClassType notionalIntf =
(IntersectionClassType)types.makeCompoundType(supertypes.toList());
notionalIntf.allparams_field = targs.toList();
notionalIntf.tsym.flags_field |= INTERFACE;
return notionalIntf.tsym;
}
private void reportIntersectionError(DiagnosticPosition pos, String key, Object... args) {
resultInfo.checkContext.report(pos, diags.fragment("bad.intersection.target.for.functional.expr",
diags.fragment(key, args)));
}
};
private Type fallbackDescriptorType(JCExpression tree) {
switch (tree.getTag()) {
case LAMBDA:
@ -2586,7 +2615,7 @@ public class Attr extends JCTree.Visitor {
Type target;
Type desc;
if (pt() != Type.recoveryType) {
target = checkIntersectionTarget(that, pt(), resultInfo.checkContext);
target = targetChecker.visit(pt(), that);
desc = types.findDescriptorType(target);
chk.checkFunctionalInterface(that, target);
} else {
@ -2594,7 +2623,7 @@ public class Attr extends JCTree.Visitor {
desc = fallbackDescriptorType(that);
}
setFunctionalInfo(that, pt(), desc, resultInfo.checkContext.inferenceContext());
setFunctionalInfo(that, pt(), desc, target, resultInfo.checkContext.inferenceContext());
List<Type> argtypes = desc.getParameterTypes();
Pair<Symbol, Resolve.ReferenceLookupHelper> refResult =
@ -2789,19 +2818,24 @@ public class Attr extends JCTree.Visitor {
* might contain inference variables, we might need to register an hook in the
* current inference context.
*/
private void setFunctionalInfo(final JCFunctionalExpression fExpr, final Type pt, final Type descriptorType, InferenceContext inferenceContext) {
private void setFunctionalInfo(final JCFunctionalExpression fExpr, final Type pt,
final Type descriptorType, final Type primaryTarget, InferenceContext inferenceContext) {
if (inferenceContext.free(descriptorType)) {
inferenceContext.addFreeTypeListener(List.of(pt, descriptorType), new FreeTypeListener() {
public void typesInferred(InferenceContext inferenceContext) {
setFunctionalInfo(fExpr, pt, inferenceContext.asInstType(descriptorType), inferenceContext);
setFunctionalInfo(fExpr, pt, inferenceContext.asInstType(descriptorType),
inferenceContext.asInstType(primaryTarget), inferenceContext);
}
});
} else {
ListBuffer<TypeSymbol> targets = ListBuffer.lb();
if (pt.hasTag(CLASS)) {
if (pt.isCompound()) {
targets.append(primaryTarget.tsym); //this goes first
for (Type t : ((IntersectionClassType)pt()).interfaces_field) {
targets.append(t.tsym);
if (t != primaryTarget) {
targets.append(t.tsym);
}
}
} else {
targets.append(pt.tsym);

View File

@ -216,9 +216,14 @@ compiler.misc.descriptor.throws=\
compiler.misc.no.suitable.functional.intf.inst=\
cannot infer functional interface descriptor for {0}
# 0: message segment
compiler.misc.bad.intersection.target.for.functional.expr=\
bad intersection type target for lambda or method reference\n\
{0}
# 0: type
compiler.misc.secondary.bound.must.be.marker.intf=\
secondary bound {0} must be a marker interface
compiler.misc.not.an.intf.component=\
component type {0} is not an interface
# 0: symbol kind, 1: message segment
compiler.err.invalid.mref=\

View File

@ -395,6 +395,9 @@ public class RichDiagnosticFormatter extends
@Override
public String visitClassSymbol(ClassSymbol s, Locale locale) {
if (s.type.isCompound()) {
return visit(s.type, locale);
}
String name = nameSimplifier.simplify(s);
if (name.length() == 0 ||
!getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
@ -583,7 +586,11 @@ public class RichDiagnosticFormatter extends
@Override
public Void visitClassSymbol(ClassSymbol s, Void ignored) {
nameSimplifier.addUsage(s);
if (s.type.isCompound()) {
typePreprocessor.visit(s.type);
} else {
nameSimplifier.addUsage(s);
}
return null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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,8 +22,9 @@
*/
// key: compiler.err.prob.found.req
// key: compiler.misc.secondary.bound.must.be.marker.intf
// key: compiler.misc.bad.intersection.target.for.functional.expr
// key: compiler.misc.not.an.intf.component
class SecondaryBoundMustBeMarkerInterface {
Runnable r = (Runnable & Comparable<?>)()->{};
class NotAnInterfaceComponent {
Object o = (Object & Runnable) ()-> { };
}

View File

@ -25,7 +25,7 @@
* @test
* @bug 8002099
* @summary Add support for intersection types in cast expression
* @compile/fail/ref=Intersection01.out -XDrawDiagnostics Intersection01.java
* @compile Intersection01.java
*/
class Intersection01 {

View File

@ -1,3 +0,0 @@
Intersection01.java:36:45: compiler.err.prob.found.req: (compiler.misc.not.a.functional.intf.1: java.io.Serializable, (compiler.misc.no.abstracts: kindname.interface, java.io.Serializable))
Intersection01.java:38:45: compiler.err.prob.found.req: (compiler.misc.not.a.functional.intf.1: java.io.Serializable, (compiler.misc.no.abstracts: kindname.interface, java.io.Serializable))
2 errors

View File

@ -28,10 +28,11 @@
*/
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
@ -45,37 +46,45 @@ public class IntersectionTargetTypeTest {
enum BoundKind {
INTF,
CLASS,
SAM,
ZAM;
CLASS;
}
enum MethodKind {
NONE,
ABSTRACT,
DEFAULT;
NONE(false),
ABSTRACT_M(true),
DEFAULT_M(false),
ABSTRACT_G(true),
DEFAULT_G(false);
boolean isAbstract;
MethodKind(boolean isAbstract) {
this.isAbstract = isAbstract;
}
}
enum TypeKind {
A("interface A { }\n", "A", BoundKind.ZAM),
B("interface B { default void m() { } }\n", "B", BoundKind.ZAM),
C("interface C { void m(); }\n", "C", BoundKind.SAM),
D("interface D extends B { }\n", "D", BoundKind.ZAM),
E("interface E extends C { }\n", "E", BoundKind.SAM),
F("interface F extends C { void g(); }\n", "F", BoundKind.INTF),
G("interface G extends B { void g(); }\n", "G", BoundKind.SAM),
H("interface H extends A { void g(); }\n", "H", BoundKind.SAM),
A("interface A { }\n", "A", BoundKind.INTF, MethodKind.NONE),
B("interface B { default void m() { } }\n", "B", BoundKind.INTF, MethodKind.DEFAULT_M),
C("interface C { void m(); }\n", "C", BoundKind.INTF, MethodKind.ABSTRACT_M),
D("interface D extends B { }\n", "D", BoundKind.INTF, MethodKind.DEFAULT_M),
E("interface E extends C { }\n", "E", BoundKind.INTF, MethodKind.ABSTRACT_M),
F("interface F extends C { void g(); }\n", "F", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.ABSTRACT_M),
G("interface G extends B { void g(); }\n", "G", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.DEFAULT_M),
H("interface H extends A { void g(); }\n", "H", BoundKind.INTF, MethodKind.ABSTRACT_G),
OBJECT("", "Object", BoundKind.CLASS),
STRING("", "String", BoundKind.CLASS);
String declStr;
String typeStr;
BoundKind boundKind;
MethodKind[] methodKinds;
private TypeKind(String declStr, String typeStr, BoundKind boundKind) {
private TypeKind(String declStr, String typeStr, BoundKind boundKind, MethodKind... methodKinds) {
this.declStr = declStr;
this.typeStr = typeStr;
this.boundKind = boundKind;
this.methodKinds = methodKinds;
}
boolean compatibleSupertype(TypeKind tk) {
@ -263,14 +272,22 @@ public class IntersectionTargetTypeTest {
boolean errorExpected = !cInfo.wellFormed();
if (ek.isFunctional) {
//first bound must be a SAM
errorExpected |= cInfo.types[0].boundKind != BoundKind.SAM;
if (cInfo.types.length > 1) {
//additional bounds must be ZAMs
for (int i = 1; i < cInfo.types.length; i++) {
errorExpected |= cInfo.types[i].boundKind != BoundKind.ZAM;
List<MethodKind> mks = new ArrayList<>();
for (TypeKind tk : cInfo.types) {
if (tk.boundKind == BoundKind.CLASS) {
errorExpected = true;
break;
} else {
mks = mergeMethods(mks, Arrays.asList(tk.methodKinds));
}
}
int abstractCount = 0;
for (MethodKind mk : mks) {
if (mk.isAbstract) {
abstractCount++;
}
}
errorExpected |= abstractCount != 1;
}
if (errorExpected != diagChecker.errorFound) {
@ -281,6 +298,32 @@ public class IntersectionTargetTypeTest {
}
}
List<MethodKind> mergeMethods(List<MethodKind> l1, List<MethodKind> l2) {
List<MethodKind> mergedMethods = new ArrayList<>(l1);
for (MethodKind mk2 : l2) {
boolean add = !mergedMethods.contains(mk2);
switch (mk2) {
case ABSTRACT_G:
add = add && !mergedMethods.contains(MethodKind.DEFAULT_G);
break;
case ABSTRACT_M:
add = add && !mergedMethods.contains(MethodKind.DEFAULT_M);
break;
case DEFAULT_G:
mergedMethods.remove(MethodKind.ABSTRACT_G);
case DEFAULT_M:
mergedMethods.remove(MethodKind.ABSTRACT_M);
case NONE:
add = false;
break;
}
if (add) {
mergedMethods.add(mk2);
}
}
return mergedMethods;
}
static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
boolean errorFound;