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:
parent
ea55015155
commit
da9dd76b20
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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=\
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) ()-> { };
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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.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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user