diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/BoxedPrimitive.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/BoxedPrimitive.java index 61c137e61c..f23b6a13b5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/BoxedPrimitive.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/BoxedPrimitive.java @@ -4,8 +4,6 @@ package net.sourceforge.pmd.lang.java.types; -import static java.util.Collections.emptyList; - import org.pcollections.PSet; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; @@ -26,7 +24,7 @@ final class BoxedPrimitive extends ClassTypeImpl { // constructor called by JPrimitiveType, exactly once per type system and per primitive BoxedPrimitive(TypeSystem factory, JClassSymbol boxType, JPrimitiveType unboxed, PSet typeAnnots) { - super(factory, boxType, emptyList(), true, typeAnnots); // not erased + super(factory, boxType, typeAnnots); this.unboxed = unboxed; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ClassTypeImpl.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ClassTypeImpl.java index 2b640b66bc..194093c6d8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ClassTypeImpl.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ClassTypeImpl.java @@ -28,14 +28,13 @@ import net.sourceforge.pmd.lang.java.types.JVariableSig.FieldSig; import net.sourceforge.pmd.util.CollectionUtil; class ClassTypeImpl implements JClassType { - private final @Nullable JClassType enclosingType; private final JClassSymbol symbol; private final TypeSystem ts; private final List typeArgs; private final PSet typeAnnotations; - private final boolean isDecl; + private final TypeGenericity genericity; private JClassType superClass; private List interfaces; @@ -53,7 +52,7 @@ class ClassTypeImpl implements JClassType { * and isDecl is false, this will represent a raw type. * If empty and isDecl is true, this will represent a * generic type declaration. - * @param isDecl Choose a bias towards generic type declaration or raw + * @param isRaw Choose a bias towards generic type declaration or raw * type. If the [rawType] param has no type parameters, * then this parameter makes *no difference*. * @@ -61,22 +60,53 @@ class ClassTypeImpl implements JClassType { * as the type's type parameters * @throws IllegalArgumentException if any type argument is of a primitive type. */ - ClassTypeImpl(TypeSystem ts, JClassSymbol symbol, List typeArgs, boolean isDecl, PSet typeAnnotations) { - this(ts, null, symbol, typeArgs, typeAnnotations, isDecl); + ClassTypeImpl(TypeSystem ts, JClassSymbol symbol, List typeArgs, boolean isRaw, PSet typeAnnotations) { + this(ts, null, symbol, typeArgs, typeAnnotations, isRaw); } - private ClassTypeImpl(TypeSystem ts, JClassType enclosing, JClassSymbol symbol, List typeArgs, PSet typeAnnotations, boolean isDecl) { + private ClassTypeImpl(TypeSystem ts, JClassType enclosing, JClassSymbol symbol, List typeArgs, PSet typeAnnotations, boolean isRaw) { this.typeAnnotations = typeAnnotations; validateParams(enclosing, symbol, typeArgs); this.ts = ts; this.symbol = symbol; this.typeArgs = typeArgs; - this.isDecl = isDecl; this.enclosingType = enclosing != null ? enclosing : makeEnclosingOf(symbol); + + this.genericity = computeGenericity(isRaw); + + } + + // Special ctor for boxed primitives and other specials that are built + // during initialization of TypeSystem. This cannot call JClassSymbol#isGeneric, + // because that would trigger parsing of the class file while the type system is not ready + protected ClassTypeImpl(TypeSystem ts, JClassSymbol symbol, PSet typeAnnotations) { + this.typeAnnotations = typeAnnotations; + this.ts = ts; + this.symbol = symbol; + this.typeArgs = emptyList(); + this.enclosingType = null; + this.genericity = TypeGenericity.NON_GENERIC; + } + + private @NonNull TypeGenericity computeGenericity(boolean isRaw) { + boolean isGeneric = symbol.isGeneric(); + if (enclosingType != null && enclosingType.isRaw()) { + return TypeGenericity.RAW; + } else if (typeArgs.isEmpty()) { + if (isGeneric && isRaw) { + return TypeGenericity.RAW; + } else if (isGeneric) { + return TypeGenericity.GENERIC_TYPEDECL; + } else { + return TypeGenericity.NON_GENERIC; + } + } else { + return TypeGenericity.GENERIC_PARAMETERIZED; + } } private JClassType makeEnclosingOf(JClassSymbol sym) { @@ -110,7 +140,7 @@ class ClassTypeImpl implements JClassType { if (newTypeAnnots.isEmpty() && this.typeAnnotations.isEmpty()) { return this; } - return new ClassTypeImpl(ts, enclosingType, symbol, typeArgs, newTypeAnnots, isDecl); + return new ClassTypeImpl(ts, enclosingType, symbol, typeArgs, newTypeAnnots, isRaw()); } @Override @@ -139,52 +169,78 @@ class ClassTypeImpl implements JClassType { } } - static JTypeMirror eraseToRaw(JTypeMirror m, Substitution typeSubst) { - // todo pretty sure this can be written m.subst(typeSubst).let { if (it === m) it else it.erasure } - if (TypeOps.mentionsAny(m, typeSubst.getMap().keySet())) { - return m.getErasure(); + /** + * Given a type appearing in a member, and the given substitution + */ + static JTypeMirror eraseToRaw(JTypeMirror t, Substitution typeSubst) { + if (TypeOps.mentionsAny(t, typeSubst.getMap().keySet())) { + return t.getErasure(); } else { - // less brutal than erasure, - // some parameterized types should be kept, if they don't depend - // on type parameters of this method - return m.subst(typeSubst); + // This type does not depend on any of the type variables to erase. + return t; + } + } + + /** + * Given a type appearing in a member of the given owner, erase + * the member type if the owner is raw. The type needs not be erased + * if it is generic but does not mention any of the type variables + * to erase. + */ + static JTypeMirror maybeEraseMemberType(JClassType owner, JTypeMirror t) { + if (owner.isRaw() && TypeOps.mentionsAny(t, owner.getTypeParamSubst().getMap().keySet())) { + return t.getErasure(); + } else { + // This type does not depend on any of the type variables to erase. + return t; + } + } + + static List maybeEraseMemberType(JClassType owner, List ts) { + if (owner.isRaw()) { + return map(ts, t -> maybeEraseMemberType(owner, t)); + } else { + // This type does not depend on any of the type variables to erase. + return ts; } } @Override - public JClassType selectInner(JClassSymbol symbol, List targs, PSet typeAnnotations) { + public final JClassType selectInner(JClassSymbol symbol, List targs, PSet typeAnnotations) { return new ClassTypeImpl(ts, this, symbol, CollectionUtil.defensiveUnmodifiableCopy(targs), typeAnnotations, - this.isDecl); + isRaw()); } @Override - public boolean isRaw() { - return !isDecl && isGeneric() && typeArgs.isEmpty() - || getEnclosingType() != null && getEnclosingType().isRaw(); + public final boolean isRaw() { + return genericity == TypeGenericity.RAW; } @Override - public boolean isGenericTypeDeclaration() { - return isDecl && isGeneric() && typeArgs.isEmpty(); + public final boolean isGenericTypeDeclaration() { + return genericity == TypeGenericity.GENERIC_TYPEDECL; } @Override - public boolean isParameterizedType() { - return isGeneric() && !typeArgs.isEmpty(); + public final boolean isParameterizedType() { + return genericity == TypeGenericity.GENERIC_PARAMETERIZED; } @Override - public boolean isGeneric() { - return symbol.isGeneric(); + public final boolean isGeneric() { + return genericity != TypeGenericity.NON_GENERIC; } @Override public JClassType getGenericTypeDeclaration() { - return isGeneric() ? withTypeArguments(getFormalTypeParams()) : this; + if (isGenericTypeDeclaration() || !isGeneric()) { + return this; + } + return new ClassTypeImpl(ts, symbol, emptyList(), false, typeAnnotations); } @Override @@ -222,7 +278,7 @@ class ClassTypeImpl implements JClassType { if (expected == 0 && typeArgs.isEmpty() && this.typeArgs.isEmpty()) { return this; // non-generic } - return new ClassTypeImpl(ts, symbol, CollectionUtil.defensiveUnmodifiableCopy(typeArgs), false, typeAnnotations); + return new ClassTypeImpl(ts, symbol, CollectionUtil.defensiveUnmodifiableCopy(typeArgs), true, typeAnnotations); } @Override @@ -261,7 +317,7 @@ class ClassTypeImpl implements JClassType { private JClassType getDeclaredClass(JClassSymbol inner) { if (Modifier.isStatic(inner.getModifiers())) { - return new ClassTypeImpl(ts, null, inner, emptyList(), typeAnnotations, true); + return new ClassTypeImpl(ts, null, inner, emptyList(), typeAnnotations, isRaw()); } else { return selectInner(inner, emptyList()); } @@ -281,7 +337,7 @@ class ClassTypeImpl implements JClassType { JClassSymbol declaredClass = symbol.getDeclaredClass(simpleName); if (declaredClass != null) { if (Modifier.isStatic(declaredClass.getModifiers())) { - return new ClassTypeImpl(ts, null, declaredClass, emptyList(), HashTreePSet.empty(), true); + return new ClassTypeImpl(ts, null, declaredClass, emptyList(), HashTreePSet.empty(), isRaw()); } else { return selectInner(declaredClass, emptyList()); } @@ -406,4 +462,10 @@ class ClassTypeImpl implements JClassType { } } + private enum TypeGenericity { + RAW, + GENERIC_TYPEDECL, + GENERIC_PARAMETERIZED, + NON_GENERIC + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ErasedClassType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ErasedClassType.java index 42d2e89152..6edc515738 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ErasedClassType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/ErasedClassType.java @@ -14,7 +14,7 @@ import net.sourceforge.pmd.lang.java.symbols.SymbolicValue.SymAnnot; final class ErasedClassType extends ClassTypeImpl { ErasedClassType(TypeSystem typeSystem, JClassSymbol symbol, PSet typeAnnots) { - super(typeSystem, symbol, Collections.emptyList(), false, typeAnnots); + super(typeSystem, symbol, Collections.emptyList(), true, typeAnnots); } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/JClassType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/JClassType.java index 3ab2d81302..abe8781e3f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/JClassType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/JClassType.java @@ -255,6 +255,7 @@ public interface JClassType extends JTypeMirror { /** * Return the list of declared nested classes. They are substituted * with the actual type arguments of this type, if it is parameterized. + * They are raw if this type is raw. * Does not look into supertypes. */ List getDeclaredClasses(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java index b14eb76f3c..5996623a42 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java @@ -304,7 +304,7 @@ public final class TypeSystem { private JClassType addSpecial(Class klass, Map shared) { JClassSymbol sym = getBootStrapSymbol(klass); - JClassType nonErased = new ClassTypeImpl(this, sym, emptyList(), false, HashTreePSet.empty()); + JClassType nonErased = new ClassTypeImpl(this, sym, HashTreePSet.empty()); shared.put(sym, nonErased); return nonErased; } @@ -455,7 +455,7 @@ public final class TypeSystem { assert component != null : "the symbol necessarily has an array component symbol"; return arrayType(component, classSym); } else { - return new ClassTypeImpl(this, classSym, emptyList(), !isErased, HashTreePSet.empty()); + return new ClassTypeImpl(this, classSym, emptyList(), isErased, HashTreePSet.empty()); } } else if (symbol instanceof JTypeParameterSymbol) { return ((JTypeParameterSymbol) symbol).getTypeMirror(); @@ -519,7 +519,7 @@ public final class TypeSystem { return rawType(klass); // note this ensures that OBJECT and such is preserved } // if the type arguments are mismatched, the constructor will throw - return new ClassTypeImpl(this, klass, CollectionUtil.defensiveUnmodifiableCopy(typeArgs), false, HashTreePSet.empty()); + return new ClassTypeImpl(this, klass, CollectionUtil.defensiveUnmodifiableCopy(typeArgs), true, HashTreePSet.empty()); } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/ClassTypeImplTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/ClassTypeImplTest.kt index 7bf53619d6..3033056626 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/ClassTypeImplTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/ClassTypeImplTest.kt @@ -5,29 +5,16 @@ package net.sourceforge.pmd.lang.java.types -import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec -import io.kotest.inspectors.forNone import io.kotest.matchers.shouldBe -import io.kotest.property.Exhaustive -import io.kotest.property.checkAll -import io.kotest.property.exhaustive.ints -import io.kotest.property.forAll -import net.sourceforge.pmd.lang.ast.test.shouldBeA -import net.sourceforge.pmd.lang.java.ast.ParserTestCtx -import net.sourceforge.pmd.lang.java.symbols.JClassSymbol -import net.sourceforge.pmd.lang.java.symbols.internal.UnresolvedClassStore +import net.sourceforge.pmd.lang.ast.test.IntelliMarker +import net.sourceforge.pmd.lang.ast.test.shouldBe import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol -import net.sourceforge.pmd.lang.java.types.TypeConversion.* -import net.sourceforge.pmd.lang.java.types.TypeOps.Convertibility.* -import net.sourceforge.pmd.lang.java.types.testdata.ComparableList -import net.sourceforge.pmd.lang.java.types.testdata.SomeEnum -import kotlin.test.assertTrue /** * @author Clément Fournier */ -class ClassTypeImplTest : FunSpec({ +class ClassTypeImplTest : IntelliMarker,FunSpec({ val ts = testTypeSystem with(TypeDslOf(ts)) { @@ -36,7 +23,7 @@ class ClassTypeImplTest : FunSpec({ test("Test repeated withTypeArguments on unresolved type") { - val sym = ts.createUnresolvedAsmSymbol("does.not.Exist") as JClassSymbol + val sym = ts.createUnresolvedAsmSymbol("does.not.Exist") val t = ts.declaration(sym) as JClassType t.withTypeArguments(listOf(t_String)).typeArgs shouldBe listOf(t_String) t.withTypeArguments(listOf(t_String)) @@ -44,6 +31,13 @@ class ClassTypeImplTest : FunSpec({ } + test("Test generic type decl") { + t_List::isRaw shouldBe true + val `t_List{T}` = t_List.genericTypeDeclaration + `t_List{T}`::isGenericTypeDeclaration shouldBe true + + } + } }