Skip to content
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

Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -412,6 +412,11 @@ public static String toString(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 String toString() {
/**
* 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 Target requiredTarget() {
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 TypeKind getKind() {
*/
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) {

This comment has been minimized.

@mcimadamore

mcimadamore Aug 12, 2021
Collaborator

isn't isReferenceProjection always false here (you have just created the class)

This comment has been minimized.

@vicente-romero-oracle

vicente-romero-oracle Aug 12, 2021
Author Contributor

not really, because this constructor is used again to build the reference, so I have to add it to the condition to avoid stackoverflow errors

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 TypeTag getTag() {
@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 @@
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) {

This comment has been minimized.

@mcimadamore

mcimadamore Aug 4, 2021
Collaborator

Are you sure the check does what the comment says?

This comment has been minimized.

@vicente-romero-oracle

vicente-romero-oracle Aug 5, 2021
Author Contributor

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()) {

This comment has been minimized.

@mcimadamore

mcimadamore Aug 10, 2021
Collaborator

I agree that here you need 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 boolean hasSameBounds(ForAll t, ForAll s) {
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>