Skip to content
Draft
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
10 changes: 9 additions & 1 deletion java-frontend/src/main/java/org/sonar/java/model/JParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2620,15 +2620,23 @@ private JavaTree.AnnotatedTypeTree convertSimpleType(SimpleType e) {

private JavaTree.UnionTypeTreeImpl convertUnionType(UnionType e) {
QualifiedIdentifierListTreeImpl alternatives = QualifiedIdentifierListTreeImpl.emptyList();
ITypeBinding[] alternativeBindings = new ITypeBinding[e.types().size()];
for (int i = 0; i < e.types().size(); i++) {
Type o = (Type) e.types().get(i);
alternatives.add(convertType(o));
TypeTree typeTree = convertType(o);
alternatives.add(typeTree);
if (typeTree instanceof AbstractTypedTree typedTree && typedTree.typeBinding != null) {
alternativeBindings[i] = typedTree.typeBinding;
} else {
alternativeBindings[i] = o.resolveBinding();
}
if (i < e.types().size() - 1) {
alternatives.separators().add(firstTokenAfter(o, TerminalToken.TokenNameOR));
}
}
JavaTree.UnionTypeTreeImpl t = new JavaTree.UnionTypeTreeImpl(alternatives);
t.typeBinding = e.resolveBinding();
t.alternativeBindings = alternativeBindings;
return t;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public final class JSema implements Sema {
private final Map<Symbol.TypeSymbol, JInitializerBlockSymbol> staticInitializerBlockSymbols = new HashMap<>();
private final Map<IAnnotationBinding, JSymbolMetadata.JAnnotationInstance> annotations = new HashMap<>();
private final Map<String, Type> nameToTypeCache = new HashMap<>();
final Map<ITypeBinding, ITypeBinding[]> unionTypeAlternatives = new HashMap<>();

JSema(AST ast) {
this.ast = ast;
Expand Down
77 changes: 59 additions & 18 deletions java-frontend/src/main/java/org/sonar/java/model/JType.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,34 +210,57 @@ public String fullyQualifiedName() {
return fullyQualifiedName;
}

private static String fullyQualifiedName(ITypeBinding typeBinding) {
String qualifiedName;
private String fullyQualifiedName(ITypeBinding typeBinding) {
String qualifiedName = baseQualifiedName(typeBinding);

if (typeBinding.isIntersectionType()) {
return buildIntersectionTypeName(typeBinding, qualifiedName);
} else if (isUnionType()) {
return buildUnionTypeName(typeBinding, qualifiedName);
}
return qualifiedName;
}

private String baseQualifiedName(ITypeBinding typeBinding) {
if (typeBinding.isNullType()) {
qualifiedName = "<nulltype>";
return "<nulltype>";
} else if (typeBinding.isPrimitive()) {
qualifiedName = typeBinding.getName();
return typeBinding.getName();
} else if (typeBinding.isArray()) {
qualifiedName = fullyQualifiedName(typeBinding.getComponentType()) + "[]";
return fullyQualifiedName(typeBinding.getComponentType()) + "[]";
} else if (typeBinding.isCapture()) {
qualifiedName = "!capture!";
return "!capture!";
} else if (typeBinding.isTypeVariable()) {
qualifiedName = typeBinding.getName();
return typeBinding.getName();
} else {
qualifiedName = typeBinding.getBinaryName();
if (qualifiedName == null) {
String binaryName = typeBinding.getBinaryName();
if (binaryName == null) {
// e.g. anonymous class in unreachable code
qualifiedName = typeBinding.getKey();
return typeBinding.getKey();
}
return binaryName;
}
if (typeBinding.isIntersectionType()) {
TreeSet<String> intersectionTypes = new TreeSet<>();
intersectionTypes.add(qualifiedName);
for (ITypeBinding typeBound : typeBinding.getTypeBounds()) {
intersectionTypes.add(fullyQualifiedName(typeBound));
}
qualifiedName = String.join(" & ", intersectionTypes);
}

private String buildIntersectionTypeName(ITypeBinding typeBinding, String baseName) {
TreeSet<String> intersectionTypes = new TreeSet<>();
intersectionTypes.add(baseName);
for (ITypeBinding typeBound : typeBinding.getTypeBounds()) {
intersectionTypes.add(fullyQualifiedName(typeBound));
}
return qualifiedName;
return String.join(" & ", intersectionTypes);
}

private String buildUnionTypeName(ITypeBinding typeBinding, String baseName) {
ITypeBinding[] alternatives = sema.unionTypeAlternatives.get(typeBinding);
if (alternatives == null) {
return baseName;
}
TreeSet<String> unionTypes = new TreeSet<>();
for (ITypeBinding alternative : alternatives) {
unionTypes.add(fullyQualifiedName(alternative));
}
return String.join(" | ", unionTypes);
}

@Override
Expand All @@ -258,6 +281,24 @@ public Type[] getIntersectionTypes() {
return types;
}

@Override
public boolean isUnionType() {
return sema.unionTypeAlternatives.containsKey(typeBinding);
}

@Override
public Type[] getUnionTypes() {
ITypeBinding[] alternatives = sema.unionTypeAlternatives.get(typeBinding);
if (alternatives == null) {
return new Type[] { this };
}
Type[] types = new Type[alternatives.length];
for (int i = 0; i < alternatives.length; i++) {
types[i] = sema.type(alternatives[i]);
}
return types;
}

/**
* @see JSymbol#name()
*/
Expand Down
10 changes: 10 additions & 0 deletions java-frontend/src/main/java/org/sonar/java/model/JavaTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.sonar.java.model.expression.AssessableExpressionTree;
import org.sonar.java.model.expression.TypeArgumentListTreeImpl;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.AnnotationTree;
import org.sonar.plugins.java.api.tree.ArrayTypeTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
Expand Down Expand Up @@ -476,6 +477,7 @@ public List<Tree> children() {

public static class UnionTypeTreeImpl extends AbstractTypedTree implements UnionTypeTree {
private final ListTree<TypeTree> typeAlternatives;
ITypeBinding[] alternativeBindings;

public UnionTypeTreeImpl(QualifiedIdentifierListTreeImpl typeAlternatives) {
this.typeAlternatives = Objects.requireNonNull(typeAlternatives);
Expand Down Expand Up @@ -505,6 +507,14 @@ public List<Tree> children() {
public List<AnnotationTree> annotations() {
return Collections.emptyList();
}

@Override
public Type symbolType() {
if (typeBinding != null && alternativeBindings != null) {
root.sema.unionTypeAlternatives.put(typeBinding, alternativeBindings);
}
return super.symbolType();
}
}

public static class NotImplementedTreeImpl extends AssessableExpressionTree {
Expand Down
10 changes: 10 additions & 0 deletions java-frontend/src/main/java/org/sonar/java/model/Symbols.java
Original file line number Diff line number Diff line change
Expand Up @@ -469,5 +469,15 @@ public boolean isIntersectionType() {
public Type[] getIntersectionTypes() {
return new Type[] { this };
}

@Override
public boolean isUnionType() {
return false;
}

@Override
public Type[] getUnionTypes() {
return new Type[] { this };
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,21 @@ interface ArrayType extends Type {
*/
Type[] getIntersectionTypes();

/**
* Check if this type is a union type. For example, return true for the type of 'e' in the following code:
* <code><pre>
* try { } catch (IOException | SQLException e) { }
*</pre></code>
*/
boolean isUnionType();

/**
* This method returns more than one type when {@link #isUnionType()} is true. For example, it
* returns {@code ["java.io.IOException", "java.sql.SQLException"] } for the type of 'e' in the following code:
* <code><pre>
* try { } catch (IOException | SQLException e) { }
*</pre></code>
*/
Type[] getUnionTypes();

}
Original file line number Diff line number Diff line change
Expand Up @@ -1497,11 +1497,69 @@ void type_union() {
Type symbolType = t.symbolType();
assertThat(symbolType).isNotNull();
assertThat(symbolType.isUnknown()).isFalse();
// "fullyQualifiedName()" should be unique for each different type, like for example "java.lang.MatchException | java.lang.NumberFormatException"
// this will be fixed by SONARJAVA-5718
assertThat(symbolType.fullyQualifiedName()).isEqualTo("java.lang.RuntimeException");
assertThat(symbolType.getIntersectionTypes()).extracting(Type::fullyQualifiedName)
.containsExactly("java.lang.RuntimeException");
// "fullyQualifiedName()" should be unique for each different type
assertThat(symbolType.fullyQualifiedName()).isEqualTo("java.lang.MatchException | java.lang.NumberFormatException");
assertThat(symbolType.isUnionType()).isTrue();
assertThat(symbolType.getUnionTypes()).extracting(Type::fullyQualifiedName)
.containsExactly("java.lang.MatchException", "java.lang.NumberFormatException");
}

@Test
void type_union_with_three_alternatives() {
CompilationUnitTree cu = test("class C { void m() { try { } catch (MatchException | NumberFormatException | ArithmeticException v) { } } }");
ClassTree c = (ClassTree) cu.types().get(0);
MethodTree m = (MethodTree) c.members().get(0);
TryStatementTree s = (TryStatementTree) m.block().body().get(0);
VariableTreeImpl v = (VariableTreeImpl) s.catches().get(0).parameter();
AbstractTypedTree t = (AbstractTypedTree) v.type();
Type symbolType = t.symbolType();
assertThat(symbolType.isUnionType()).isTrue();
// FQN should be sorted alphabetically
assertThat(symbolType.fullyQualifiedName()).isEqualTo("java.lang.ArithmeticException | java.lang.MatchException | java.lang.NumberFormatException");
assertThat(symbolType.getUnionTypes()).hasSize(3);
// getUnionTypes() returns types in source order, not sorted
assertThat(symbolType.getUnionTypes()).extracting(Type::fullyQualifiedName)
.containsExactly("java.lang.MatchException", "java.lang.NumberFormatException", "java.lang.ArithmeticException");
}

@Test
void type_union_ordering() {
// Test that union types are sorted alphabetically in FQN
CompilationUnitTree cu = test("class C { void m() { try { } catch (NumberFormatException | MatchException v) { } } }");
ClassTree c = (ClassTree) cu.types().get(0);
MethodTree m = (MethodTree) c.members().get(0);
TryStatementTree s = (TryStatementTree) m.block().body().get(0);
VariableTreeImpl v = (VariableTreeImpl) s.catches().get(0).parameter();
AbstractTypedTree t = (AbstractTypedTree) v.type();
Type symbolType = t.symbolType();
// Should be sorted: MatchException comes before NumberFormatException
assertThat(symbolType.fullyQualifiedName()).isEqualTo("java.lang.MatchException | java.lang.NumberFormatException");
}

@Test
void non_union_type_returns_false_for_isUnionType() {
CompilationUnitTree cu = test("class C { void m() { try { } catch (NumberFormatException v) { } } }");
ClassTree c = (ClassTree) cu.types().get(0);
MethodTree m = (MethodTree) c.members().get(0);
TryStatementTree s = (TryStatementTree) m.block().body().get(0);
VariableTreeImpl v = (VariableTreeImpl) s.catches().get(0).parameter();
AbstractTypedTree t = (AbstractTypedTree) v.type();
Type symbolType = t.symbolType();
assertThat(symbolType.isUnionType()).isFalse();
assertThat(symbolType.getUnionTypes()).hasSize(1);
assertThat(symbolType.getUnionTypes()[0]).isEqualTo(symbolType);
}

@Test
void primitive_type_is_not_union() {
CompilationUnitTree cu = test("class C { void m(int x) { } }");
ClassTree c = (ClassTree) cu.types().get(0);
MethodTree m = (MethodTree) c.members().get(0);
VariableTreeImpl v = (VariableTreeImpl) m.parameters().get(0);
Type symbolType = v.symbol().type();
assertThat(symbolType.isUnionType()).isFalse();
assertThat(symbolType.getUnionTypes()).hasSize(1);
assertThat(symbolType.getUnionTypes()[0]).isEqualTo(symbolType);
}

@Test
Expand Down