Skip to content
Permalink
Browse files
universal type variables: initial prototype
Reviewed-by: mcimadamore
  • Loading branch information
Vicente Romero committed Aug 12, 2021
1 parent f1a651e commit 3e896fafc69ce6b769ad9685d5de0212951224af
Showing 22 changed files with 755 additions and 39 deletions.
@@ -412,6 +412,11 @@ public static EnumSet<Flag> asFlagSet(long flags) {
*/
public static final long NON_SEALED = 1L<<63; // ClassSymbols

/**
* Flag to indicate that the type variables is universal
*/
public static final long UNIVERSAL = 1L<<61; // TypeVariableSymbols

// Encodings for extended flags stored using attributes
/**
* Flag to indicate that the primitive class is reference default.
@@ -324,7 +324,12 @@ public enum LintCategory {
/**
* Warn about use of preview features.
*/
PREVIEW("preview");
PREVIEW("preview"),

/**
* Warn about use of universal type variables.
*/
UNIVERSAL("universal");

LintCategory(String option) {
this(option, false);
@@ -234,6 +234,7 @@ public enum Feature {
CASE_NULL(JDK17, Fragments.FeatureCaseNull, DiagKind.NORMAL),
PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
REDUNDANT_STRICTFP(JDK17),
UNIVERSAL_TVARS(JDK18, Fragments.FeatureUniversalTvars, DiagKind.PLURAL),
;

enum DiagKind {
@@ -1943,12 +1943,32 @@ 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;

protected boolean isReferenceProjection = false;

// redundant for now but helpful for debug reasons
private boolean isUniversal;

public TypeVar(Name name, Symbol owner, Type lower) {
this(name, owner, lower, false);
}

public TypeVar(Name name, Symbol owner, Type lower, boolean isUniversal) {
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.isUniversal = isUniversal;
if (isUniversal && !isReferenceProjection) {
referenceProjection();
}
}

public TypeVar(TypeSymbol tsym, Type bound, Type lower) {
@@ -1957,15 +1977,25 @@ 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;
if (isUniversal && !isReferenceProjection) {
referenceProjection();
}
}

@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(); }

@@ -1989,7 +2019,11 @@ public <R,S> R accept(Type.Visitor<R,S> v, S s) {
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Type getUpperBound() { return _bound; }

public void setUpperBound(Type bound) { this._bound = bound; }
public void setUpperBound(Type bound) {
this._bound = bound;
if (projection != null)
projection.setUpperBound(bound);
}

int rank_field = -1;

@@ -2021,6 +2055,26 @@ public boolean isNullOrReference() {
public <R, P> R accept(TypeVisitor<R, P> v, P p) {
return v.visitTypeVariable(this, p);
}

@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 +2094,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 +2105,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) {
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()) {
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) {
@@ -3552,7 +3607,11 @@ public Type visitTypeVar(TypeVar t, Void ignored) {
for (List<Type> from = this.from, to = this.to;
from.nonEmpty();
from = from.tail, to = to.tail) {
if (t.equalsIgnoreMetadata(from.head)) {
if (t.equalsIgnoreMetadata(from.head) ||
allowUniversalTVars &&
!t.isValueProjection() &&
from.head.hasTag(TYPEVAR) &&
t.equalsIgnoreMetadata(((TypeVar)from.head).referenceProjection())) {
return to.head.withTypeVar(t);
}
}
@@ -3704,7 +3763,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>

0 comments on commit 3e896fa

Please sign in to comment.