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
langtools

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

@ -610,7 +610,7 @@ public class Types {
/** /**
* Scope filter used to skip methods that should be ignored (such as methods * 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> { 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"> // <editor-fold defaultstate="collapsed" desc="isSubtype">
/** /**
* Is t an unchecked subtype of s? * Is t an unchecked subtype of s?

@ -2273,7 +2273,7 @@ public class Attr extends JCTree.Visitor {
Type lambdaType; Type lambdaType;
if (pt() != Type.recoveryType) { if (pt() != Type.recoveryType) {
target = checkIntersectionTarget(that, target, resultInfo.checkContext); target = targetChecker.visit(target, that);
lambdaType = types.findDescriptorType(target); lambdaType = types.findDescriptorType(target);
chk.checkFunctionalInterface(that, target); chk.checkFunctionalInterface(that, target);
} else { } else {
@ -2281,7 +2281,7 @@ public class Attr extends JCTree.Visitor {
lambdaType = fallbackDescriptorType(that); lambdaType = fallbackDescriptorType(that);
} }
setFunctionalInfo(that, pt(), lambdaType, resultInfo.checkContext.inferenceContext()); setFunctionalInfo(that, pt(), lambdaType, target, resultInfo.checkContext.inferenceContext());
if (lambdaType.hasTag(FORALL)) { if (lambdaType.hasTag(FORALL)) {
//lambda expression target desc cannot be a generic method //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 //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) { private Type fallbackDescriptorType(JCExpression tree) {
switch (tree.getTag()) { switch (tree.getTag()) {
case LAMBDA: case LAMBDA:
@ -2586,7 +2615,7 @@ public class Attr extends JCTree.Visitor {
Type target; Type target;
Type desc; Type desc;
if (pt() != Type.recoveryType) { if (pt() != Type.recoveryType) {
target = checkIntersectionTarget(that, pt(), resultInfo.checkContext); target = targetChecker.visit(pt(), that);
desc = types.findDescriptorType(target); desc = types.findDescriptorType(target);
chk.checkFunctionalInterface(that, target); chk.checkFunctionalInterface(that, target);
} else { } else {
@ -2594,7 +2623,7 @@ public class Attr extends JCTree.Visitor {
desc = fallbackDescriptorType(that); desc = fallbackDescriptorType(that);
} }
setFunctionalInfo(that, pt(), desc, resultInfo.checkContext.inferenceContext()); setFunctionalInfo(that, pt(), desc, target, resultInfo.checkContext.inferenceContext());
List<Type> argtypes = desc.getParameterTypes(); List<Type> argtypes = desc.getParameterTypes();
Pair<Symbol, Resolve.ReferenceLookupHelper> refResult = Pair<Symbol, Resolve.ReferenceLookupHelper> refResult =
@ -2789,20 +2818,25 @@ public class Attr extends JCTree.Visitor {
* might contain inference variables, we might need to register an hook in the * might contain inference variables, we might need to register an hook in the
* current inference context. * 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)) { if (inferenceContext.free(descriptorType)) {
inferenceContext.addFreeTypeListener(List.of(pt, descriptorType), new FreeTypeListener() { inferenceContext.addFreeTypeListener(List.of(pt, descriptorType), new FreeTypeListener() {
public void typesInferred(InferenceContext inferenceContext) { public void typesInferred(InferenceContext inferenceContext) {
setFunctionalInfo(fExpr, pt, inferenceContext.asInstType(descriptorType), inferenceContext); setFunctionalInfo(fExpr, pt, inferenceContext.asInstType(descriptorType),
inferenceContext.asInstType(primaryTarget), inferenceContext);
} }
}); });
} else { } else {
ListBuffer<TypeSymbol> targets = ListBuffer.lb(); ListBuffer<TypeSymbol> targets = ListBuffer.lb();
if (pt.hasTag(CLASS)) { if (pt.hasTag(CLASS)) {
if (pt.isCompound()) { if (pt.isCompound()) {
targets.append(primaryTarget.tsym); //this goes first
for (Type t : ((IntersectionClassType)pt()).interfaces_field) { for (Type t : ((IntersectionClassType)pt()).interfaces_field) {
if (t != primaryTarget) {
targets.append(t.tsym); targets.append(t.tsym);
} }
}
} else { } else {
targets.append(pt.tsym); targets.append(pt.tsym);
} }

@ -216,9 +216,14 @@ compiler.misc.descriptor.throws=\
compiler.misc.no.suitable.functional.intf.inst=\ compiler.misc.no.suitable.functional.intf.inst=\
cannot infer functional interface descriptor for {0} 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 # 0: type
compiler.misc.secondary.bound.must.be.marker.intf=\ compiler.misc.not.an.intf.component=\
secondary bound {0} must be a marker interface component type {0} is not an interface
# 0: symbol kind, 1: message segment # 0: symbol kind, 1: message segment
compiler.err.invalid.mref=\ compiler.err.invalid.mref=\

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

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.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 { class NotAnInterfaceComponent {
Runnable r = (Runnable & Comparable<?>)()->{}; Object o = (Object & Runnable) ()-> { };
} }

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

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

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