New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
universal type variables: initial prototype #521
Changes from 3 commits
e28bdb6
39e7775
3b30083
fe1a449
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
@@ -1943,12 +1943,30 @@ public static class TypeVar extends Type implements TypeVariable { | ||
*/ | ||
public Type lower; | ||
|
||
/** if this type variable is universal then it could also have a link to a pure reference | ||
* type variable, it is important to know that a universal type variable and its | ||
* corresponding referenceTypeVar share the same tsym. So if it is needed to double check if | ||
* a type variable is universal or not, we need to check its type not the type of its tsym | ||
*/ | ||
public TypeVar projection = null; | ||
|
||
public boolean isReferenceProjection; | ||
|
||
// redundant for now but helpful for debug reasons | ||
public boolean isUniversal; | ||
|
||
public TypeVar(Name name, Symbol owner, Type lower) { | ||
this(name, owner, lower, false, false); | ||
} | ||
|
||
public TypeVar(Name name, Symbol owner, Type lower, boolean isUniversal, boolean isReferenceProjection) { | ||
super(null, TypeMetadata.EMPTY); | ||
Assert.checkNonNull(lower); | ||
tsym = new TypeVariableSymbol(0, name, this, owner); | ||
tsym = new TypeVariableSymbol(isUniversal ? UNIVERSAL : 0, name, this, owner); | ||
this.setUpperBound(null); | ||
this.lower = lower; | ||
this.isReferenceProjection = isReferenceProjection; | ||
this.isUniversal = isUniversal; | ||
} | ||
|
||
public TypeVar(TypeSymbol tsym, Type bound, Type lower) { | ||
@@ -1957,15 +1975,22 @@ public TypeVar(TypeSymbol tsym, Type bound, Type lower) { | ||
|
||
public TypeVar(TypeSymbol tsym, Type bound, Type lower, | ||
TypeMetadata metadata) { | ||
this(tsym, bound, lower, metadata, false); | ||
} | ||
|
||
public TypeVar(TypeSymbol tsym, Type bound, Type lower, | ||
TypeMetadata metadata, boolean isReferenceProjection) { | ||
super(tsym, metadata); | ||
Assert.checkNonNull(lower); | ||
this.setUpperBound(bound); | ||
this.lower = lower; | ||
this.isReferenceProjection = isReferenceProjection; | ||
this.isUniversal = (tsym.flags_field & UNIVERSAL) != 0; | ||
} | ||
|
||
@Override | ||
public TypeVar cloneWithMetadata(TypeMetadata md) { | ||
return new TypeVar(tsym, getUpperBound(), lower, md) { | ||
return new TypeVar(tsym, getUpperBound(), lower, md, isReferenceProjection) { | ||
@Override | ||
public Type baseType() { return TypeVar.this.baseType(); } | ||
|
||
@@ -2021,6 +2046,36 @@ public boolean isNullOrReference() { | ||
public <R, P> R accept(TypeVisitor<R, P> v, P p) { | ||
return v.visitTypeVariable(this, p); | ||
} | ||
|
||
@Override | ||
public Type withTypeVar(Type t) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uhm - isn't this method used on wildcard types, and it all other types is supposed to just return |
||
if (t.hasTag(TYPEVAR) && | ||
((TypeVar)t).isReferenceProjection() && | ||
projection != null) { | ||
return projection; | ||
} | ||
return this; | ||
} | ||
|
||
@Override | ||
public TypeVar referenceProjection() { | ||
if (projection == null) { | ||
projection = new TypeVar(tsym, _bound, lower, metadata, true); | ||
} | ||
return projection; | ||
} | ||
|
||
public boolean isUniversal() { | ||
return ((tsym.flags_field & UNIVERSAL) != 0); | ||
} | ||
|
||
public boolean isReferenceProjection() { | ||
return isReferenceProjection; | ||
} | ||
|
||
public boolean isValueProjection() { | ||
return isUniversal() && !isReferenceProjection(); | ||
} | ||
} | ||
|
||
/** A captured type variable comes from wildcards which can have | ||
@@ -2040,6 +2095,7 @@ public CapturedType(Name name, | ||
this.lower = Assert.checkNonNull(lower); | ||
this.setUpperBound(upper); | ||
this.wildcard = wildcard; | ||
this.isReferenceProjection = wildcard.bound != null ? wildcard.bound.isReferenceProjection : false; | ||
} | ||
|
||
public CapturedType(TypeSymbol tsym, | ||
@@ -2050,6 +2106,7 @@ public CapturedType(TypeSymbol tsym, | ||
TypeMetadata metadata) { | ||
super(tsym, bound, lower, metadata); | ||
this.wildcard = wildcard; | ||
this.isReferenceProjection = wildcard.bound != null ? wildcard.bound.isReferenceProjection : false; | ||
} | ||
|
||
@Override | ||
@@ -95,6 +95,7 @@ public class Types { | ||
final boolean allowDefaultMethods; | ||
final boolean mapCapturesToBounds; | ||
final boolean allowValueBasedClasses; | ||
final boolean allowUniversalTVars; | ||
final Check chk; | ||
final Enter enter; | ||
JCDiagnostic.Factory diags; | ||
@@ -125,7 +126,9 @@ protected Types(Context context) { | ||
diags = JCDiagnostic.Factory.instance(context); | ||
noWarnings = new Warner(null); | ||
Options options = Options.instance(context); | ||
Preview preview = Preview.instance(context); | ||
allowValueBasedClasses = options.isSet("allowValueBasedClasses"); | ||
allowUniversalTVars = Feature.UNIVERSAL_TVARS.allowedInSource(source); | ||
} | ||
// </editor-fold> | ||
|
||
@@ -608,6 +611,11 @@ public boolean isConvertible(Type t, Type s, Warner warn) { | ||
|
||
boolean tValue = t.isPrimitiveClass(); | ||
boolean sValue = s.isPrimitiveClass(); | ||
if (allowUniversalTVars && (s.hasTag(TYPEVAR)) && ((TypeVar)s).isValueProjection() && | ||
(t.hasTag(BOT) || t.hasTag(TYPEVAR) && !((TypeVar)t).isValueProjection())) { | ||
warn.warn(LintCategory.UNIVERSAL); | ||
return true; | ||
} | ||
if (tValue != sValue) { | ||
return tValue ? | ||
isSubtype(t.referenceProjection(), s) : | ||
@@ -1014,6 +1022,32 @@ public boolean isPrimitiveClass(Type t) { | ||
return t != null && t.isPrimitiveClass(); | ||
} | ||
|
||
@FunctionalInterface | ||
public interface SubtypeTestFlavor { | ||
boolean subtypeTest(Type t, Type s, Warner warn); | ||
} | ||
|
||
public boolean isBoundedBy(Type t, Type s, SubtypeTestFlavor subtypeTestFlavor) { | ||
return isBoundedBy(t, s, noWarnings, subtypeTestFlavor); | ||
} | ||
|
||
/** | ||
* Is type t bounded by s? | ||
*/ | ||
public boolean isBoundedBy(Type t, Type s, Warner warn, SubtypeTestFlavor subtypeTestFlavor) { | ||
boolean result = subtypeTestFlavor.subtypeTest(t, s, warn); | ||
if (allowUniversalTVars && !result) { | ||
if (isPrimitiveClass(t)) { | ||
return isBoundedBy(t.referenceProjection(), s, warn, subtypeTestFlavor); | ||
} else if (t.hasTag(TYPEVAR) && ((TypeVar)t).isUniversal()) { | ||
return isBoundedBy(t.getUpperBound(), s, warn, subtypeTestFlavor); | ||
} else if (s.hasTag(TYPEVAR) && ((TypeVar)s).isUniversal()) { | ||
return isBoundedBy(t, s.getLowerBound(), warn, subtypeTestFlavor); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
// <editor-fold defaultstate="collapsed" desc="isSubtype"> | ||
/** | ||
* Is t an unchecked subtype of s? | ||
@@ -1051,6 +1085,9 @@ private boolean isSubtypeUncheckedInternal(Type t, Type s, boolean capture, Warn | ||
} | ||
} else if (isSubtype(t, s, capture)) { | ||
return true; | ||
} else if (allowUniversalTVars && t.hasTag(TYPEVAR) && s.hasTag(TYPEVAR) && t.tsym == s.tsym) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure the check does what the comment says? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will remove the comment, probably a self comment I guess |
||
warn.warn(LintCategory.UNIVERSAL); | ||
return true; | ||
} else if (t.hasTag(TYPEVAR)) { | ||
return isSubtypeUncheckedInternal(t.getUpperBound(), s, false, warn); | ||
} else if (!s.isRaw()) { | ||
@@ -1102,6 +1139,9 @@ public final boolean isSubtypeNoCapture(Type t, Type s) { | ||
public boolean isSubtype(Type t, Type s, boolean capture) { | ||
if (t.equalsIgnoreMetadata(s)) | ||
return true; | ||
if (t.hasTag(TYPEVAR) && s.hasTag(TYPEVAR) && t.tsym == s.tsym) { | ||
return true; | ||
} | ||
if (s.isPartial()) | ||
return isSuperType(s, t); | ||
|
||
@@ -1143,6 +1183,9 @@ public Boolean visitType(Type t, Type s) { | ||
case TYPEVAR: | ||
return isSubtypeNoCapture(t.getUpperBound(), s); | ||
case BOT: | ||
if (allowUniversalTVars && s.hasTag(TYPEVAR) && ((TypeVar)s).isValueProjection()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that here you need |
||
warnStack.head.warn(LintCategory.UNIVERSAL); | ||
} | ||
return | ||
s.hasTag(BOT) || (s.hasTag(CLASS) && !isPrimitiveClass(s)) || | ||
s.hasTag(ARRAY) || s.hasTag(TYPEVAR); | ||
@@ -1641,8 +1684,10 @@ public Boolean visitWildcardType(WildcardType t, Type s) { | ||
// --------------------------------------------------------------------------- | ||
return isSameWildcard(t, s) | ||
|| isCaptureOf(s, t) | ||
|| ((t.isExtendsBound() || isSubtypeNoCapture(wildLowerBound(t), wildLowerBound(s))) && | ||
(t.isSuperBound() || isSubtypeNoCapture(wildUpperBound(s), wildUpperBound(t)))); | ||
|| ((t.isExtendsBound() || isBoundedBy(wildLowerBound(t), wildLowerBound(s), | ||
(t1, s1, w) -> isSubtypeNoCapture(t1, s1))) && | ||
(t.isSuperBound() || isBoundedBy(wildUpperBound(s), wildUpperBound(t), | ||
(t1, s1, w) -> isSubtypeNoCapture(t1, s1)))); | ||
} | ||
} | ||
|
||
@@ -1655,6 +1700,16 @@ public Boolean visitUndetVar(UndetVar t, Type s) { | ||
} | ||
} | ||
|
||
@Override | ||
public Boolean visitTypeVar(TypeVar t, Type s) { | ||
if (s.hasTag(TYPEVAR)) { | ||
TypeVar other = (TypeVar)s; | ||
if (allowUniversalTVars && t.isValueProjection() != other.isValueProjection() && t.tsym == other.tsym) | ||
return true; | ||
} | ||
return isSameType(t, s); | ||
} | ||
|
||
@Override | ||
public Boolean visitErrorType(ErrorType t, Type s) { | ||
return true; | ||
@@ -2075,7 +2130,7 @@ public boolean notSoftSubtype(Type t, Type s) { | ||
if (!s.hasTag(WILDCARD)) | ||
s = cvarUpperBound(s); | ||
|
||
return !isSubtype(t, relaxBound(s)); | ||
return !isBoundedBy(t, relaxBound(s), (t1, s1, w) -> isSubtype(t1, s1)); | ||
} | ||
|
||
private Type relaxBound(Type t) { | ||
@@ -3555,6 +3610,13 @@ public Type visitTypeVar(TypeVar t, Void ignored) { | ||
if (t.equalsIgnoreMetadata(from.head)) { | ||
return to.head.withTypeVar(t); | ||
} | ||
if (allowUniversalTVars && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about this - the main issue here seems that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure I follow you here, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I mean is that the newly added code seems to workaround the fact that in some cases we do not call |
||
!t.isValueProjection() && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about this here - "not value projection might also mean it's a regular tvar". Doesn't this test want to check for |
||
from.head.hasTag(TYPEVAR) && | ||
((TypeVar)from.head).projection != null && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this test be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doing this change right now breaks the build, I think it is because we generate reference projections for universal type vars in a lazy way, so some universal type variables could have its projection field set to null There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ugh - I understand - moving forward it'd be nice not to depend on tricky init semantics. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, will fix this |
||
t.equalsIgnoreMetadata(((TypeVar)from.head).projection)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here, we should call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep I agree |
||
return to.head.withTypeVar(t); | ||
} | ||
} | ||
return t; | ||
} | ||
@@ -3704,7 +3766,11 @@ public List<Type> newInstances(List<Type> tvars) { | ||
private static final TypeMapping<Void> newInstanceFun = new TypeMapping<Void>() { | ||
@Override | ||
public TypeVar visitTypeVar(TypeVar t, Void _unused) { | ||
return new TypeVar(t.tsym, t.getUpperBound(), t.getLowerBound(), t.getMetadata()); | ||
TypeVar newTV = new TypeVar(t.tsym, t.getUpperBound(), t.getLowerBound(), t.getMetadata(), t.isReferenceProjection); | ||
if (t.projection != null) { | ||
newTV.referenceProjection(); | ||
} | ||
return newTV; | ||
} | ||
}; | ||
// </editor-fold> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructor probably should not take the
isReferenceProjection
as this one creates a new tsym and you want the reference projection to share the same symbol - so you probably are always passingfalse
here.