Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4387,7 +4387,7 @@ compiler.err.unsupported.null.restriction=\
# 0: type, 1: type
compiler.warn.suspicious.nullness.conversion=\
Suspicious nullness conversion\n\
required: {0}!\n\
required: {0}\n\
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this hack is really the goal of this PR -- now all null-restricted types are rendered correctly in the formatter machinery. In fact -- see some changes in the raw diagnostics in some negative tests, where null-restriction now (correctly) appear.

found: {1}

# 0: type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import com.sun.tools.javac.jvm.Target;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.tree.JCTree.JCNullableTypeExpression.NullMarker;
import com.sun.tools.javac.tree.Pretty;

import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticType.*;
Expand Down Expand Up @@ -200,7 +201,7 @@ else if (arg instanceof Iterable<?> iterable && !(arg instanceof Path)) {
return formatIterable(d, iterable, l);
}
else if (arg instanceof Type type) {
return printer.visit(type.stripMetadata(), l);
return printer.visit(normalize(type), l);
}
else if (arg instanceof JCDiagnostic.AnnotatedType type) {
return printer.visit(type.type(), l);
Expand Down Expand Up @@ -235,6 +236,14 @@ else if (arg instanceof Tag tag) {
}
}
//where
private Type normalize(Type type) {
Type t2 = type.stripMetadata();
if (t2 != type && type.getNullMarker() == NullMarker.NOT_NULL) {
t2 = t2.asNullMarked(NullMarker.NOT_NULL);
}
return t2;
}

private String expr2String(JCExpression tree) {
switch(tree.getTag()) {
case PARENS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package com.sun.tools.javac.util;

import java.nio.file.Path;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
Expand All @@ -42,11 +43,13 @@
import com.sun.tools.javac.code.Type.*;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.util.RichDiagnosticFormatter.WhereClauses.Kind;

import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.TypeTag.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import static com.sun.tools.javac.code.TypeTag.TYPEVAR;
import static com.sun.tools.javac.util.LayoutCharacters.*;
import static com.sun.tools.javac.util.RichDiagnosticFormatter.RichConfiguration.*;

Expand Down Expand Up @@ -84,19 +87,103 @@ public class RichDiagnosticFormatter extends
/* type/symbol printer used by this formatter */
private RichPrinter printer;

private static class WhereClauses {
private final Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses;
/**
* This class models the "where clauses" associated to a javac rich diagnostic.
* There are several kinds of where clauses, based on the type they refer to:
* <ul>
* <li>type-variable where clauses;</li>
* <li>captured type-variable where clauses; and</li>
* <li>intersection type where clauses.</li>
* </ul>
* This class ensures that the correct amount of where clauses is added for all the
* types mentioned in a javac diagnostic. Note that where clauses must differentiate
* between type-variables with same name, but different symbols.
*/
class WhereClauses {
private final Map<Kind, Map<Type, JCDiagnostic>> whereClausesByKind;
private final Map<Name, List<Type>> typeVarsByName;

WhereClauses() {
Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses = new EnumMap<>(WhereClauseKind.class);
for (WhereClauseKind kind : WhereClauseKind.values()) {
whereClauses.put(kind, new LinkedHashMap<>());
Map<Kind, Map<Type, JCDiagnostic>> whereClausesByType = new EnumMap<>(Kind.class);
for (Kind kind : Kind.values()) {
whereClausesByType.put(kind, new LinkedHashMap<>());
}
this.whereClauses = whereClauses;
this.whereClausesByKind = whereClausesByType;
this.typeVarsByName = new LinkedHashMap<>();
}

int indexOf(Type type, Kind kind) {
Collection<Type> typesToSearch = kind == Kind.TYPEVAR ?
typeVarsByName.getOrDefault(type.tsym.name, List.nil()) :
whereClausesByKind.get(kind).keySet();
int index = 1;
for (Type t : typesToSearch) {
if (sameWhereClause(t, type)) {
return index;
}
index++;
}
return -1;
}

public Map<Type, JCDiagnostic> get(WhereClauseKind kind) {
return whereClauses.get(kind);
private boolean sameWhereClause(Type t, Type s) {
return t.hasTag(TYPEVAR) ?
t.tsym == s.tsym :
types.isSameType(t, s);
}

boolean isUniqueTypeVar(TypeVar typevar) {
int found = typeVarsByName
.getOrDefault(typevar.tsym.name, List.nil())
.length();
if (found < 1)
throw new AssertionError("Missing type variable in where clause: " + typevar);
return found == 1;
}

void put(Type type, Kind kind, JCDiagnostic diagnostic) {
whereClausesByKind.get(kind).put(type, diagnostic);
if (kind == Kind.TYPEVAR) {
List<Type> tvars = typeVarsByName.getOrDefault(type.tsym.name, List.nil());
typeVarsByName.put(type.tsym.name, tvars.append(type));
}
}

Map<Type, JCDiagnostic> map(Kind kind) {
return whereClausesByKind.get(kind);
}

/**
* This enum defines all possible kinds of where clauses that can be
* attached by a rich diagnostic formatter to a given diagnostic
*/
enum Kind {

/**
* where clause regarding a type variable
*/
TYPEVAR("where.description.typevar"),
/**
* where clause regarding a captured type
*/
CAPTURED("where.description.captured"),
/**
* where clause regarding an intersection type
*/
INTERSECTION("where.description.intersection");

/**
* resource key for this where clause kind
*/
private final String key;

Kind(String key) {
this.key = key;
}

String key() {
return key;
}
}
}

Expand Down Expand Up @@ -235,76 +322,23 @@ else if (arg instanceof Iterable<?> iterable && !(arg instanceof Path)) {
*/
protected List<JCDiagnostic> getWhereClauses() {
List<JCDiagnostic> clauses = List.nil();
for (WhereClauseKind kind : WhereClauseKind.values()) {
for (Kind kind : Kind.values()) {
List<JCDiagnostic> lines = List.nil();
for (Map.Entry<Type, JCDiagnostic> entry : whereClauses.get(kind).entrySet()) {
for (Map.Entry<Type, JCDiagnostic> entry : whereClauses.map(kind).entrySet()) {
lines = lines.prepend(entry.getValue());
}
if (!lines.isEmpty()) {
String key = kind.key();
if (lines.size() > 1)
key += ".1";
JCDiagnostic d = diags.fragment(key, whereClauses.get(kind).keySet());
JCDiagnostic d = diags.fragment(key, whereClauses.map(kind).keySet());
d = new JCDiagnostic.MultilineDiagnostic(d, lines.reverse());
clauses = clauses.prepend(d);
}
}
return clauses.reverse();
}

private int indexOf(Type type, WhereClauseKind kind) {
int index = 1;
for (Type t : whereClauses.get(kind).keySet()) {
if (t.tsym == type.tsym) {
return index;
}
if (kind != WhereClauseKind.TYPEVAR ||
t.toString().equals(type.toString())) {
index++;
}
}
return -1;
}

private boolean unique(TypeVar typevar) {
typevar = (TypeVar) typevar.stripMetadata();

int found = 0;
for (Type t : whereClauses.get(WhereClauseKind.TYPEVAR).keySet()) {
if (t.stripMetadata().toString().equals(typevar.toString())) {
found++;
}
}
if (found < 1)
throw new AssertionError("Missing type variable in where clause: " + typevar);
return found == 1;
}
//where
/**
* This enum defines all possible kinds of where clauses that can be
* attached by a rich diagnostic formatter to a given diagnostic
*/
enum WhereClauseKind {

/** where clause regarding a type variable */
TYPEVAR("where.description.typevar"),
/** where clause regarding a captured type */
CAPTURED("where.description.captured"),
/** where clause regarding an intersection type */
INTERSECTION("where.description.intersection");

/** resource key for this where clause kind */
private final String key;

WhereClauseKind(String key) {
this.key = key;
}

String key() {
return key;
}
}

// <editor-fold defaultstate="collapsed" desc="name simplifier">
/**
* A name simplifier keeps track of class names usages in order to determine
Expand Down Expand Up @@ -378,7 +412,7 @@ public String localize(Locale locale, String key, Object... args) {

@Override
public String capturedVarId(CapturedType t, Locale locale) {
return indexOf(t, WhereClauseKind.CAPTURED) + "";
return whereClauses.indexOf(t, Kind.CAPTURED) + "";
}

@Override
Expand All @@ -394,7 +428,7 @@ public String visitCapturedType(CapturedType t, Locale locale) {
if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
return localize(locale,
"compiler.misc.captured.type",
indexOf(t, WhereClauseKind.CAPTURED));
whereClauses.indexOf(t, Kind.CAPTURED));
}
else
return super.visitCapturedType(t, locale);
Expand All @@ -406,7 +440,7 @@ public String visitClassType(ClassType t, Locale locale) {
getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
return localize(locale,
"compiler.misc.intersection.type",
indexOf(t, WhereClauseKind.INTERSECTION));
whereClauses.indexOf(t, Kind.INTERSECTION));
}
else
return super.visitClassType(t, locale);
Expand All @@ -427,14 +461,14 @@ else if (longform)

@Override
public String visitTypeVar(TypeVar t, Locale locale) {
if (unique(t) ||
if (whereClauses.isUniqueTypeVar(t) ||
!getConfiguration().isEnabled(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES)) {
return t.toString();
}
else {
return localize(locale,
"compiler.misc.type.var",
t.toString(), indexOf(t, WhereClauseKind.TYPEVAR));
t.toString(), whereClauses.indexOf(t, Kind.TYPEVAR));
}
}

Expand Down Expand Up @@ -535,10 +569,10 @@ public Void visitType(Type t, Void ignored) {

@Override
public Void visitCapturedType(CapturedType t, Void ignored) {
if (indexOf(t, WhereClauseKind.CAPTURED) == -1) {
if (whereClauses.indexOf(t, Kind.CAPTURED) == -1) {
String suffix = t.lower == syms.botType ? ".1" : "";
JCDiagnostic d = diags.fragment("where.captured"+ suffix, t, t.getUpperBound(), t.lower, t.wildcard);
whereClauses.get(WhereClauseKind.CAPTURED).put(t, d);
whereClauses.put(t, Kind.CAPTURED, d);
visit(t.wildcard);
visit(t.lower);
visit(t.getUpperBound());
Expand All @@ -549,11 +583,11 @@ public Void visitCapturedType(CapturedType t, Void ignored) {
@Override
public Void visitClassType(ClassType t, Void ignored) {
if (t.isCompound()) {
if (indexOf(t, WhereClauseKind.INTERSECTION) == -1) {
if (whereClauses.indexOf(t, Kind.INTERSECTION) == -1) {
Type supertype = types.supertype(t);
List<Type> interfaces = types.interfaces(t);
JCDiagnostic d = diags.fragment(Fragments.WhereIntersection(t, interfaces.prepend(supertype)));
whereClauses.get(WhereClauseKind.INTERSECTION).put(t, d);
whereClauses.put(t, Kind.INTERSECTION, d);
visit(supertype);
visit(interfaces);
}
Expand Down Expand Up @@ -584,7 +618,7 @@ public Void visitClassType(ClassType t, Void ignored) {
@Override
public Void visitTypeVar(TypeVar t, Void ignored) {
t = (TypeVar)t.stripMetadata();
if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) {
if (whereClauses.indexOf(t, Kind.TYPEVAR) == -1) {
//access the bound type and skip error types
Type bound = t.getUpperBound();
while ((bound instanceof ErrorType errorType))
Expand All @@ -607,14 +641,14 @@ public Void visitTypeVar(TypeVar t, Void ignored) {
JCDiagnostic d = diags.fragment("where.typevar" +
(boundErroneous ? ".1" : ""), t, bounds,
kindName(t.tsym.location()), t.tsym.location());
whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
whereClauses.put(t, Kind.TYPEVAR, d);
symbolPreprocessor.visit(t.tsym.location(), null);
visit(bounds);
} else {
Assert.check(!boundErroneous);
//this is a fresh (synthetic) tvar
JCDiagnostic d = diags.fragment(Fragments.WhereFreshTypevar(t, bounds));
whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
whereClauses.put(t, Kind.TYPEVAR, d);
visit(bounds);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* @test /nodynamiccopyright/
* @summary Check that type annotations in intersection types do not lead to duplicated where clauses
* @compile/fail/ref=TestDuplicatedIntersection.out -XDrawDiagnostics -XDrawDiagnostics --diags=formatterOptions=where TestDuplicatedIntersection.java
*/

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.List;

class TestDuplicatedIntersection {
@Target(ElementType.TYPE_USE)
@interface A { }

interface S1 { }
interface S2 { }

interface B extends @A S1, @A S2 { }
interface C extends @A S1, @A S2 { }

interface D extends S1, S2 { }
interface E extends S1, S2 { }

<X> X pick(X a, X b) { return null; }

void test1(List<B> b, List<C> c, List<D> d, List<E> e) {
var x = pick(b, c);
var y = pick(d, e);
g(x, y);
}

void g(String s) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
TestDuplicatedIntersection.java:29:9: compiler.err.cant.apply.symbol: kindname.method, g, java.lang.String, java.util.List<compiler.misc.captured.type: 1>,java.util.List<compiler.misc.captured.type: 2>, kindname.class, TestDuplicatedIntersection, (compiler.misc.arg.length.mismatch)
- compiler.misc.where.description.captured.1: compiler.misc.captured.type: 1,compiler.misc.captured.type: 2,{(compiler.misc.where.captured.1: compiler.misc.captured.type: 1, compiler.misc.intersection.type: 1, compiler.misc.type.null, ? extends compiler.misc.intersection.type: 1),(compiler.misc.where.captured.1: compiler.misc.captured.type: 2, compiler.misc.intersection.type: 1, compiler.misc.type.null, ? extends compiler.misc.intersection.type: 1)}
- compiler.misc.where.description.intersection: compiler.misc.intersection.type: 1,{(compiler.misc.where.intersection: compiler.misc.intersection.type: 1, java.lang.Object,TestDuplicatedIntersection.S1,TestDuplicatedIntersection.S2)}
1 error
Loading