diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index 614b4cdbb2b..d008c4b462f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -412,6 +412,11 @@ public static EnumSet 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. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index 08a8a0e5482..a5766fb370d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -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); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java index 294445be5f8..38bd9612d16 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java @@ -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 { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index d6e36153bb7..3d0c33b6bf5 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -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 accept(Type.Visitor 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 accept(TypeVisitor 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 diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index 00182b98bd3..b86931ca620 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -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); } // @@ -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; + } + // /** * 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 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 newInstances(List tvars) { private static final TypeMapping newInstanceFun = new TypeMapping() { @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; } }; // diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index e057f2eefcc..9b9cf851d48 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -174,6 +174,7 @@ protected Attr(Context context) { allowRecords = Feature.RECORDS.allowedInSource(source); allowPatternSwitch = (preview.isEnabled() || !preview.isPreview(Feature.PATTERN_SWITCH)) && Feature.PATTERN_SWITCH.allowedInSource(source); + allowUniversalTVars = Feature.UNIVERSAL_TVARS.allowedInSource(source); sourceName = source.name; useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning"); @@ -222,6 +223,10 @@ protected Attr(Context context) { */ private final boolean allowPatternSwitch; + /** Are universal tvars allowed? + */ + private final boolean allowUniversalTVars; + /** * Switch: warn about use of variable before declaration? * RFE: 6425594 @@ -2655,7 +2660,8 @@ public void visitApply(JCMethodInvocation tree) { && (tree.meth.hasTag(SELECT)) && ((JCFieldAccess)tree.meth).selected.hasTag(IDENT) && TreeInfo.name(((JCFieldAccess)tree.meth).selected) == names._super; - if (qualifier.tsym.isPrimitiveClass() || superCallOnPrimitiveReceiver) { + boolean qualifierIsUniversal = allowUniversalTVars && qualifier.hasTag(TYPEVAR) && ((TypeVar)qualifier).isUniversal(); + if (qualifier.tsym.isPrimitiveClass() || qualifierIsUniversal || superCallOnPrimitiveReceiver) { int argSize = argtypes.size(); Name name = symbol.name; switch (name.toString()) { @@ -2663,7 +2669,14 @@ public void visitApply(JCMethodInvocation tree) { if (argSize == 0 || (types.isConvertible(argtypes.head, syms.longType) && (argSize == 1 || (argSize == 2 && types.isConvertible(argtypes.tail.head, syms.intType))))) { - log.error(tree.pos(), Errors.PrimitiveClassDoesNotSupport(name)); + if (!qualifierIsUniversal) { + log.error(tree.pos(), Errors.PrimitiveClassDoesNotSupport(name)); + } else { + /* probably this will be a lint warning in the future, still under discussion + * will comment it for now + */ + //log.warning(tree.pos(), Warnings.MethodShouldNotBeInvokedOnUniversalTvars(name)); + } } break; case "notify": @@ -2671,7 +2684,14 @@ public void visitApply(JCMethodInvocation tree) { case "clone": case "finalize": if (argSize == 0) - log.error(tree.pos(), Errors.PrimitiveClassDoesNotSupport(name)); + if (!qualifierIsUniversal) { + log.error(tree.pos(), Errors.PrimitiveClassDoesNotSupport(name)); + } else { + /* probably this will be a lint warning in the future, still under discussion + * will comment it for now + */ + //log.warning(tree.pos(), Warnings.MethodShouldNotBeInvokedOnUniversalTvars(name)); + } break; } } @@ -3060,7 +3080,9 @@ private void visitAnonymousClassDefinition(JCNewClass tree, JCExpression clazz, for (Type t : clazztype.getTypeArguments()) { rs.checkAccessibleType(env, t); } - chk.checkParameterizationByPrimitiveClass(tree, clazztype); + if (!allowUniversalTVars) { + chk.checkParameterizationByPrimitiveClass(tree, clazztype); + } } // If we already errored, be careful to avoid a further avalanche. ErrorType answers @@ -4574,6 +4596,9 @@ private Symbol selectSym(JCFieldAccess tree, case WILDCARD: throw new AssertionError(tree); case TYPEVAR: + if (allowUniversalTVars && name == names.ref && ((TypeVar)site).isUniversal()) { + return ((TypeVar)site).referenceProjection().tsym; + } // Normally, site.getUpperBound() shouldn't be null. // It should only happen during memberEnter/attribBase // when determining the super type which *must* be @@ -5104,9 +5129,21 @@ public void visitTypeApply(JCTypeApply tree) { // Attribute functor part of application and make sure it's a class. Type clazztype = chk.checkClassType(tree.clazz.pos(), attribType(tree.clazz, env)); - - // Attribute type parameters - List actuals = attribTypes(tree.arguments, env); + List actuals; + List args = tree.arguments; + if (!allowUniversalTVars || args == null || args.isEmpty()) { + actuals = attribTypes(tree.arguments, env); + } else { + // we use argTypes2 to keep a pointer to the original list as we will use argTypes to iterate over it + List argTypes, argTypes2; + argTypes2 = argTypes = attribAnyTypes(args, env); + for (Type t : ((ClassType) clazztype.tsym.type).typarams_field) { + argTypes.head = chk.checkRefType(args.head.pos(), argTypes.head, ((TypeVar) t).isUniversal()); + args = args.tail; + argTypes = argTypes.tail; + } + actuals = argTypes2; + } if (clazztype.hasTag(CLASS)) { List formals = clazztype.tsym.type.getTypeArguments(); @@ -5290,7 +5327,7 @@ public void visitWildcard(JCWildcard tree) { Type type = (tree.kind.kind == BoundKind.UNBOUND) ? syms.objectType : attribType(tree.inner, env); - result = check(tree, new WildcardType(chk.checkRefType(tree.pos(), type, false), + result = check(tree, new WildcardType(chk.checkRefType(tree.pos(), type, allowUniversalTVars), tree.kind.kind, syms.boundClass), KindSelector.TYP, resultInfo); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index a4e5b3a1250..1a154b95ad5 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -26,6 +26,7 @@ package com.sun.tools.javac.comp; import java.util.*; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -162,6 +163,7 @@ protected Check(Context context) { allowRecords = Feature.RECORDS.allowedInSource(source); allowSealed = Feature.SEALED_CLASSES.allowedInSource(source); + allowUniversalTVars = Feature.UNIVERSAL_TVARS.allowedInSource(source); } /** Character for synthetic names @@ -201,6 +203,10 @@ protected Check(Context context) { */ private final boolean allowSealed; + /** Are universal tvars allowed + */ + private final boolean allowUniversalTVars; + /* ************************************************************************* * Errors and Warnings **************************************************************************/ @@ -266,6 +272,15 @@ public void warnUnchecked(DiagnosticPosition pos, Warning warnKey) { uncheckedHandler.report(pos, warnKey); } + /** Warn about operation with universal type variables. + * @param pos Position to be used for error reporting. + * @param warnKey A warning key. + */ + public void warnUniversalTVar(DiagnosticPosition pos, Warning warnKey) { + if (lint.isEnabled(LintCategory.UNIVERSAL)) + log.warning(LintCategory.UNIVERSAL, pos, warnKey); + } + /** Warn about unsafe vararg method decl. * @param pos Position to be used for error reporting. */ @@ -604,9 +619,11 @@ Type checkType(final DiagnosticPosition pos, final Type found, final Type req, f inferenceContext.addFreeTypeListener(List.of(req, found), solvedContext -> checkType(pos, solvedContext.asInstType(found), solvedContext.asInstType(req), checkContext)); } else { - if (found.hasTag(CLASS)) { - if (inferenceContext != infer.emptyContext) - checkParameterizationByPrimitiveClass(pos, found); + if (!allowUniversalTVars) { + if (found.hasTag(CLASS)) { + if (inferenceContext != infer.emptyContext) + checkParameterizationByPrimitiveClass(pos, found); + } } } if (req.hasTag(ERROR)) @@ -686,7 +703,7 @@ private boolean checkExtends(Type a, Type bound) { return true; } else if (!a.hasTag(WILDCARD)) { a = types.cvarUpperBound(a); - return types.isSubtype(a, bound); + return types.isBoundedBy(a, bound, (t, s, w) -> types.isSubtype(t, s)); } else if (a.isExtendsBound()) { return types.isCastable(bound, types.wildUpperBound(a), types.noWarnings); } else if (a.isSuperBound()) { @@ -848,8 +865,8 @@ Type checkRefType(DiagnosticPosition pos, Type t, boolean primitiveClassOK) { return t; else return typeTagError(pos, - diags.fragment(Fragments.TypeReqRef), - t); + diags.fragment(Fragments.TypeReqRef), + t); } /** Check that type is an identity type, i.e. not a primitive type @@ -929,7 +946,9 @@ boolean checkDisjoint(DiagnosticPosition pos, long flags, long set1, long set2) } void checkParameterizationByPrimitiveClass(DiagnosticPosition pos, Type t) { - parameterizationByPrimitiveClassChecker.visit(t, pos); + if (!allowUniversalTVars) { + parameterizationByPrimitiveClassChecker.visit(t, pos); + } } /** parameterizationByPrimitiveClassChecker: A type visitor that descends down the given type looking for instances of primitive classes @@ -1126,7 +1145,7 @@ Type checkLocalVarType(DiagnosticPosition pos, Type t, Name name) { //upward project the initializer type Type varType = types.upward(t, types.captures(t)); - if (varType.hasTag(CLASS)) { + if (!allowUniversalTVars && varType.hasTag(CLASS)) { checkParameterizationByPrimitiveClass(pos, varType); } return varType; @@ -4199,6 +4218,9 @@ public void warn(LintCategory lint) { Check.this.warnUnsafeVararg(pos(), Warnings.VarargsUnsafeUseVarargsParam(method.params.last())); } break; + case UNIVERSAL: + Check.this.warnUniversalTVar(pos(), Warnings.UniversalVariableCannotBeAssignedNull); + break; default: throw new AssertionError("Unexpected lint: " + lint); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java index b3944d386d9..2fcd6cb5152 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java @@ -565,7 +565,7 @@ protected void duplicateClass(DiagnosticPosition pos, ClassSymbol c) { public void visitTypeParameter(JCTypeParameter tree) { TypeVar a = (tree.type != null) ? (TypeVar)tree.type - : new TypeVar(tree.name, env.info.scope.owner, syms.botType); + : new TypeVar(tree.name, env.info.scope.owner, syms.botType, tree.universal); tree.type = a; if (chk.checkUnique(tree.pos(), a.tsym, env.info.scope)) { env.info.scope.enter(a.tsym); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index 2e4437a643e..c82ce691a30 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -53,6 +53,7 @@ import static com.sun.tools.javac.code.Kinds.Kind.*; import com.sun.tools.javac.code.Type.TypeVar; import static com.sun.tools.javac.code.TypeTag.BOOLEAN; +import static com.sun.tools.javac.code.TypeTag.TYPEVAR; import static com.sun.tools.javac.code.TypeTag.VOID; import static com.sun.tools.javac.comp.Flow.ThisExposability.ALLOWED; import static com.sun.tools.javac.comp.Flow.ThisExposability.BANNED; @@ -210,6 +211,7 @@ public class Flow { private Env attrEnv; private Lint lint; private final boolean allowEffectivelyFinalInInnerClasses; + private final boolean allowUniversalTVars; public static Flow instance(Context context) { Flow instance = context.get(flowKey); @@ -335,6 +337,8 @@ protected Flow(Context context) { diags = JCDiagnostic.Factory.instance(context); Source source = Source.instance(context); allowEffectivelyFinalInInnerClasses = Feature.EFFECTIVELY_FINAL_IN_INNER_CLASSES.allowedInSource(source); + Preview preview = Preview.instance(context); + allowUniversalTVars = Feature.UNIVERSAL_TVARS.allowedInSource(source); } /** @@ -1849,7 +1853,8 @@ protected boolean trackable(VarSymbol sym) { return sym.pos >= startPos && ((sym.owner.kind == MTH || sym.owner.kind == VAR || - isFinalUninitializedField(sym))); + isFinalUninitializedField(sym)) || + isUninitializedFieldOfUniversalTVar(sym)); } boolean isFinalUninitializedField(VarSymbol sym) { @@ -1862,6 +1867,13 @@ boolean isFinalUninitializedStaticField(VarSymbol sym) { return isFinalUninitializedField(sym) && sym.isStatic(); } + boolean isUninitializedFieldOfUniversalTVar(VarSymbol sym) { + return allowUniversalTVars && sym.owner.kind == TYP && + ((sym.flags() & (FINAL | HASINIT | PARAMETER)) == 0 && + classDef.sym.isEnclosedBy((ClassSymbol)sym.owner) && + sym.type.hasTag(TYPEVAR) && ((Type.TypeVar)sym.type).isValueProjection()); + } + /** Initialize new trackable variable by setting its address field * to the next available sequence number and entering it under that * index into the vars array. @@ -1972,7 +1984,11 @@ void checkInit(DiagnosticPosition pos, VarSymbol sym, Error errkey) { trackable(sym) && !inits.isMember(sym.adr) && (sym.flags_field & CLASH) == 0) { - log.error(pos, errkey); + if (isUninitializedFieldOfUniversalTVar(sym)) { + log.warning(pos, Warnings.VarMightNotHaveBeenInitialized(sym)); + } else { + log.error(pos, errkey); + } inits.incl(sym.adr); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java index 2392ac3ffae..b71c0a23fc1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java @@ -28,7 +28,6 @@ import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.code.Type.UndetVar.UndetVarListener; import com.sun.tools.javac.code.Types.TypeMapping; -import com.sun.tools.javac.comp.Attr.CheckMode; import com.sun.tools.javac.resources.CompilerProperties.Fragments; import com.sun.tools.javac.resources.CompilerProperties.Notes; import com.sun.tools.javac.tree.JCTree; @@ -1168,7 +1167,7 @@ enum IncorporationBinaryOpKind { IS_SUBTYPE() { @Override boolean apply(Type op1, Type op2, Warner warn, Types types) { - return types.isSubtypeUnchecked(op1, op2, warn); + return types.isBoundedBy(op1, op2, warn, (t, s, w) -> types.isSubtypeUnchecked(t, s, w)); } }, IS_SAME_TYPE() { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index 353c0137d38..f4a29100e1c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -1053,7 +1053,8 @@ public MethodCheckContext(boolean strict, DeferredAttrContext deferredAttrContex public boolean compatible(Type found, Type req, Warner warn) { InferenceContext inferenceContext = deferredAttrContext.inferenceContext; return strict ? - types.isSubtypeUnchecked(inferenceContext.asUndetVar(found), inferenceContext.asUndetVar(req), warn) : + types.isBoundedBy(inferenceContext.asUndetVar(found), inferenceContext.asUndetVar(req), warn, + (t, s, w) -> types.isSubtypeUnchecked(t, s, w)) : types.isConvertible(inferenceContext.asUndetVar(found), inferenceContext.asUndetVar(req), warn); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 7929ee6b97d..e37b11004e2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -194,6 +194,7 @@ protected JavacParser(ParserFactory fac, this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); this.allowPrimitiveClasses = (!preview.isPreview(Feature.PRIMITIVE_CLASSES) || preview.isEnabled()) && Feature.PRIMITIVE_CLASSES.allowedInSource(source); + this.allowUniversalTVars = Feature.UNIVERSAL_TVARS.allowedInSource(source); } protected AbstractEndPosTable newEndPosTable(boolean keepEndPositions) { @@ -237,12 +238,16 @@ protected DocCommentTable newDocCommentTable(boolean keepDocComments, ParserFact /** Switch: are primitive classes allowed in this source level? */ - boolean allowPrimitiveClasses; + boolean allowPrimitiveClasses; /** Switch: are sealed types allowed in this source level? */ boolean allowSealedTypes; + /** Switch: are primitive classes allowed in this source level? + */ + boolean allowUniversalTVars; + /** The type of the method receiver, as specified by a first "this" parameter. */ JCVariableDecl receiverParam; @@ -4704,6 +4709,12 @@ protected List typeParametersOpt() { JCTypeParameter typeParameter() { int pos = token.pos; List annos = typeAnnotationsOpt(); + boolean universal = false; + if (allowUniversalTVars && token.kind == UNIVERSAL) { + checkSourceLevel(Feature.UNIVERSAL_TVARS); + universal = true; + nextToken(); + } Name name = typeName(); ListBuffer bounds = new ListBuffer<>(); if (token.kind == EXTENDS) { @@ -4714,7 +4725,9 @@ JCTypeParameter typeParameter() { bounds.append(parseType()); } } - return toP(F.at(pos).TypeParameter(name, bounds.toList(), annos)); + return toP(F.at(pos).TypeParameter(name, bounds.toList(), annos, universal)); + // use the line below to make experiments setting all type variables as universal + //return toP(F.at(pos).TypeParameter(name, bounds.toList(), annos, true)); } /** FormalParameters = "(" [ FormalParameterList ] ")" diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java index 66b9995945d..211516de0aa 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java @@ -206,6 +206,7 @@ public enum TokenKind implements Formattable, Predicate { GTGTEQ(">>="), GTGTGTEQ(">>>="), MONKEYS_AT("@"), + UNIVERSAL("__universal"), CUSTOM; public final String name; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index a2e10ca4b46..7f2af69385a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -1420,8 +1420,8 @@ compiler.err.var.might.not.have.been.initialized=\ variable {0} might not have been initialized # 0: symbol -compiler.err.var.not.initialized.in.default.constructor=\ - variable {0} not initialized in the default constructor +compiler.warn.var.might.not.have.been.initialized=\ + variable {0} might not have been initialized # 0: symbol compiler.err.var.might.be.assigned.in.loop=\ @@ -2995,6 +2995,9 @@ compiler.misc.feature.case.null=\ compiler.misc.feature.pattern.switch=\ patterns in switch statements +compiler.misc.feature.universal.tvars=\ + universal type variables + compiler.warn.underscore.as.identifier=\ as of release 9, ''_'' is a keyword, and may not be used as an identifier @@ -3912,3 +3915,12 @@ compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class=\ compiler.note.cant.instantiate.object.directly=\ Object cannot be instantiated directly; a subclass of Object will be instantiated instead, by invoking java.util.Objects.newIdentity() + +### universal type variables + +# 0: symbol +compiler.err.var.not.initialized.in.default.constructor=\ + variable {0} not initialized in the default constructor + +compiler.warn.universal.variable.cannot.be.assigned.null=\ + variables of type universal type variable cannot be assigned null \ No newline at end of file diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index 122f54392d2..deb8f781961 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -267,6 +267,9 @@ javac.opt.Xlint.desc.varargs=\ javac.opt.Xlint.desc.preview=\ Warn about use of preview language features. +javac.opt.Xlint.desc.universal=\ + Warn about potential heap pollution issues when using universal type variables. + javac.opt.Xlint.desc.synchronization=\ Warn about synchronization attempts on instances of value-based classes. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index be5fe2c330c..f6def09ee62 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -2869,10 +2869,14 @@ public static class JCTypeParameter extends JCTree implements TypeParameterTree public List bounds; /** type annotations on type parameter */ public List annotations; - protected JCTypeParameter(Name name, List bounds, List annotations) { + /** is this an universal type variable? */ + public boolean universal; + + protected JCTypeParameter(Name name, List bounds, List annotations, boolean universal) { this.name = name; this.bounds = bounds; this.annotations = annotations; + this.universal = universal; } @Override public void accept(Visitor v) { v.visitTypeParameter(this); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java index 0262fd170fa..b59fbdef881 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java @@ -488,7 +488,7 @@ public JCTree visitTypeParameter(TypeParameterTree node, P p) { JCTypeParameter t = (JCTypeParameter) node; List annos = copy(t.annotations, p); List bounds = copy(t.bounds, p); - return M.at(t.pos).TypeParameter(t.name, bounds, annos); + return M.at(t.pos).TypeParameter(t.name, bounds, annos, t.universal); } @DefinedBy(Api.COMPILER_TREE) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java index 2669590949a..b73dc8d4879 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java @@ -584,7 +584,11 @@ public JCTypeParameter TypeParameter(Name name, List bounds) { } public JCTypeParameter TypeParameter(Name name, List bounds, List annos) { - JCTypeParameter tree = new JCTypeParameter(name, bounds, annos); + return TypeParameter(name, bounds, annos, false); + } + + public JCTypeParameter TypeParameter(Name name, List bounds, List annos, boolean universal) { + JCTypeParameter tree = new JCTypeParameter(name, bounds, annos, universal); tree.pos = pos; return tree; } @@ -1074,7 +1078,7 @@ public JCMethodDecl MethodDef(MethodSymbol m, Type mtype, JCBlock body) { */ public JCTypeParameter TypeParam(Name name, TypeVar tvar) { return (JCTypeParameter) - TypeParameter(name, Types(types.getBounds(tvar))).setPos(pos).setType(tvar); + TypeParameter(name, Types(types.getBounds(tvar)), List.nil(), tvar.isValueProjection()).setPos(pos).setType(tvar); } /** Create a list of type parameter trees from a list of type variables. diff --git a/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java b/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java index 76aefd0002e..c638a5cffbb 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java @@ -230,6 +230,7 @@ static enum TK { PUBLIC(TokenKind.PUBLIC, XDECL1 | XMODIFIER), // public TRANSIENT(TokenKind.TRANSIENT, XDECL1 | XMODIFIER), // transient VOLATILE(TokenKind.VOLATILE, XDECL1 | XMODIFIER), // volatile + UNIVERSAL(TokenKind.UNIVERSAL, XDECL1 | XMODIFIER), // __universal (Valhalla) // Declarations and type parameters (thus expressions) EXTENDS(TokenKind.EXTENDS, XEXPR|XDECL), // extends diff --git a/test/langtools/tools/javac/diags/examples.not-yet.txt b/test/langtools/tools/javac/diags/examples.not-yet.txt index a6432deeb57..6dea58c76e9 100644 --- a/test/langtools/tools/javac/diags/examples.not-yet.txt +++ b/test/langtools/tools/javac/diags/examples.not-yet.txt @@ -222,3 +222,4 @@ compiler.err.super.method.cannot.be.synchronized compiler.err.super.no.arg.constructor.must.be.empty compiler.err.generic.parameterization.with.primitive.class compiler.misc.feature.primitive.classes +compiler.misc.feature.universal.tvars diff --git a/test/langtools/tools/javac/diags/examples/UniversalCantBeAssignedNull.java b/test/langtools/tools/javac/diags/examples/UniversalCantBeAssignedNull.java new file mode 100644 index 00000000000..f74e28f39f9 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/UniversalCantBeAssignedNull.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.warn.universal.variable.cannot.be.assigned.null +// key: compiler.warn.var.might.not.have.been.initialized +// options: -Xlint:universal + +class Box<__universal T> { + T t; + void m() { t = null; } +} diff --git a/test/langtools/tools/javac/valhalla/lworld-values/universal-type-variables/UniversalTVarsCompilationTests.java b/test/langtools/tools/javac/valhalla/lworld-values/universal-type-variables/UniversalTVarsCompilationTests.java new file mode 100644 index 00000000000..e2109bca2a7 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/lworld-values/universal-type-variables/UniversalTVarsCompilationTests.java @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2021, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * UniversalTVarsCompilationTests + * + * @test + * @summary Negative compilation tests, and positive compilation (smoke) tests for universal type variables + * @library /lib/combo /tools/lib /tools/javac/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.util + * jdk.jdeps/com.sun.tools.classfile + * @build JavacTestingAbstractProcessor + * @run testng/othervm UniversalTVarsCompilationTests + */ + +import com.sun.tools.javac.util.Assert; + +import org.testng.annotations.Test; +import tools.javac.combo.CompilationTestCase; + +import static org.testng.Assert.assertEquals; + +@Test +public class UniversalTVarsCompilationTests extends CompilationTestCase { + private static String[] EMPTY_OPTIONS = {}; + + private static String[] LINT_OPTIONS = { + "-Xlint:universal" + }; + + public UniversalTVarsCompilationTests() { + setDefaultFilename("Test.java"); + } + + public void testWarningNullAssigment() { + setCompileOptions(LINT_OPTIONS); + assertOKWithWarning("compiler.warn.universal.variable.cannot.be.assigned.null", + """ + class Box<__universal T> { + T t; + void m() { t = null; } + } + """ + ); + assertOKWithWarning("compiler.warn.universal.variable.cannot.be.assigned.null", + """ + class Box<__universal T> { + T m() { return null; } + } + """ + ); + assertOKWithWarning("compiler.warn.universal.variable.cannot.be.assigned.null", + """ + class C<__universal T> { + T.ref x = null; + T get() { return x; } // warning: possible null value conversion + T.ref getRef() { return x; } // OK + } + """ + ); + + setCompileOptions(EMPTY_OPTIONS); + assertOKWithWarning("compiler.warn.var.might.not.have.been.initialized", + """ + class Box<__universal T> { + T t; + void m() { t = null; } + } + """ + ); + assertOK( + """ + import java.io.*; + class C<__universal T extends Reader> { T x = null; /* ok */ } + """ + ); + assertOK( + """ + import java.io.*; + class C { T x = null; /* ok */ } + """ + ); + assertOK( + """ + import java.io.*; + class C<__universal T extends Reader> { T.ref x = null; /* ok */ } + """ + ); + } + + public void testPosCompilations() { + setCompileOptions(EMPTY_OPTIONS); + assertOK( + """ + primitive class Point {} + + class C<__universal T> { + C cp; + } + """ + ); + assertOK( + """ + interface Shape {} + + primitive class Point implements Shape {} + + class Box<__universal T> {} + + class Test { + void m(Box lp) { + /* this invocation will provoke a subtype checking, basically a check testing if: + * `Box <: Box`, this should stress the new `isBoundedBy` relation, + * in particular it should check if `Point` is bounded by `Shape`, which is true as + * `Point.ref` isBoundedBy Shape + */ + foo(lp); + } + + void foo(Box ls) {} + } + """ + ); + assertOK( + """ + interface Shape {} + + primitive class Point implements Shape {} + + class Box<__universal T> {} + + class Test { + void m(Box lp) { + foo(lp); + } + + void foo(Box ls) {} + } + """ + ); + assertOK( + """ + import java.io.*; + + primitive class Point {} + + class C { + T x = null; + + void m() { + FileReader r = new C().x; + Point.ref p = new C().x; + } + } + """ + ); + assertOK( + """ + import java.io.*; + + primitive class Point {} + + class C<__universal T> { + T.ref x = null; + + void m() { + FileReader r = new C().x; + Point.ref p = new C().x; + Point.ref p2 = new C().x; + } + } + """ + ); + assertOK( + """ + class C<__universal T> { + T.ref x = null; + void set(T arg) { x = arg; /* ok */ } + } + """ + ); + assertOK( + """ + primitive class Point {} + + class MyList<__universal T> { + static <__universal E> MyList of(E e1) { + return null; + } + } + + class Test { + void m() { + MyList.of(new Point()); + } + } + """ + ); + + assertOK( + """ + primitive class Point {} + + class MyCollection<__universal T> {} + + class MyList<__universal T> extends MyCollection { + static <__universal E> MyList of(E e1) { + return null; + } + } + + class Test { + void m() { + MyCollection mpc = MyList.of(new Point()); + } + } + """ + ); + } + + public void testUniversalTVarFieldMustBeInit() { + setCompileOptions(EMPTY_OPTIONS); + assertOKWithWarning("compiler.warn.var.might.not.have.been.initialized", + """ + class Box<__universal T> { + T t; + Box() {} + } + """ + ); + + assertOK( + """ + class Box<__universal T> { + T.ref t; + Box() {} + } + """ + ); + } + + public void testForbiddenMethods() { + setCompileOptions(EMPTY_OPTIONS); + assertFail("compiler.err.primitive.class.does.not.support", + """ + primitive class Point {} + + class Test { + static <__universal Z> Z id(Z z) { + return z; + } + + static void main(String... args) throws Throwable { + Point p = new Point(); + id(p).wait(); + } + } + """ + ); + // this one will probably be a lint warning + /* + assertOKWithWarning("compiler.warn.method.should.not.be.invoked.on.universal.tvars", + """ + primitive class Point {} + + class Test<__universal T> { + void m(T t) throws Throwable { + t.wait(); + } + } + """ + ); + */ + } + + public void testPosCompilations2() { + setCompileOptions(EMPTY_OPTIONS); + assertOK( + """ + interface MyComparable<__universal T> { + public int compareTo(T o); + } + + primitive class Int128 implements MyComparable { + public int compareTo(Int128 i128) { + return 0; + } + } + + class Test { + <__universal Z extends MyComparable> void m(Z z) {} + void foo() { + Int128 i = new Int128(); + m(i); + } + } + """ + ); + assertOK( + """ + interface MyComparable<__universal T> {} + + primitive class Int128 implements MyComparable { + public int compareTo(Int128 i128) { + return 0; + } + } + + class Test { + <__universal Z extends MyComparable> void m(Z z) {} + void foo() { + Int128 i = new Int128(); + m(i); + } + } + """ + ); + assertOK( + """ + import java.util.*; + + interface I {} + + class Test { + <__universal T> T.ref bar() { + return null; + } + + void foo() { + List values = bar(); + } + } + """ + ); + + assertOK( + """ + class Test<__universal T> { + T.ref t; + + void m() { + this.t = null; + } + } + """ + ); + + assertOK( + """ + import java.util.*; + + class Test { + Map types = new HashMap<>(); + } + """ + ); + + assertOK( + """ + class C1 { + <__universal T> void foo(T t) {} + } + + class C2 extends C1 { + <__universal T> void foo(T.ref t) { } + } + """ + ); + + assertOK( + """ + class C1 { + <__universal T> void foo(T.ref t) {} + } + + class C2 extends C1 { + <__universal T> void foo(T t) { } + } + """ + ); + + assertOK( + """ + import java.util.function.*; + class Test<__universal T> { + T.ref field; + void foo(T t, Consumer action) { + action.accept(field = t); + } + } + """ + ); + } + + public void testUncheckedWarning() { + /* this one should generate unchecked warning + interface MyList<__universal E> {} + + class MyArrays { + @SafeVarargs + @SuppressWarnings("varargs") + public static <__universal T> MyList asList(T... a) { + return null; + } + } + + class Test<__universal T> { + MyList newList() { + return MyArrays.asList(null, null); + } + + void foo() { + MyList list = newList(); // unchecked warning + } + } + */ + } +}