From bc20be1ed8c8778e80fd2bb63363b88a811810fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bendeg=C3=BAz=20Nagy?= Date: Tue, 15 Aug 2017 23:37:08 +0200 Subject: [PATCH] Java, typeres: add basic method type inference --- .../pmd/lang/java/ast/ASTExpression.java | 8 +- .../typeresolution/ClassTypeResolver.java | 10 +- .../typeresolution/MethodTypeResolution.java | 52 +++++------ .../typeinference/TypeInferenceResolver.java | 35 +++++++ .../typeresolution/ClassTypeResolverTest.java | 91 ++++++++++++++----- .../testdata/GenericMethods.java | 14 --- .../testdata/GenericMethodsImplicit.java | 23 +++++ 7 files changed, 164 insertions(+), 69 deletions(-) delete mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethods.java create mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethodsImplicit.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java index 76eea66fb8..6eb42b49b1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java @@ -22,25 +22,25 @@ public class ASTExpression extends AbstractJavaTypeNode { } public boolean isStandAlonePrimitive() { - if(jjtGetNumChildren() != 1) { + if (jjtGetNumChildren() != 1) { return false; } ASTPrimaryExpression primaryExpression = getFirstChildOfType(ASTPrimaryExpression.class); - if(primaryExpression == null || primaryExpression.jjtGetNumChildren() != 1) { + if (primaryExpression == null || primaryExpression.jjtGetNumChildren() != 1) { return false; } ASTPrimaryPrefix primaryPrefix = primaryExpression.getFirstChildOfType(ASTPrimaryPrefix.class); - if(primaryPrefix == null || primaryPrefix.jjtGetNumChildren() != 1) { + if (primaryPrefix == null || primaryPrefix.jjtGetNumChildren() != 1) { return false; } ASTLiteral literal = primaryPrefix.getFirstChildOfType(ASTLiteral.class); - if(literal == null || literal.jjtGetNumChildren() != 0) { + if (literal == null || literal.jjtGetNumChildren() != 0) { return false; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index 4939dfe9d8..5cfdbb7f52 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -362,7 +362,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { // First try: com.package.SomeClass.staticField.otherField // Second try: com.package.SomeClass.staticField // Third try: com.package.SomeClass <- found a class! - for (String reducedImage = node.getImage(); ; ) { + for (String reducedImage = node.getImage();;) { populateType(node, reducedImage); if (node.getType() != null) { break; // we found a class! @@ -422,7 +422,13 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { Collections.emptyList(), methodArgsArity, accessingClass); - previousType = getBestMethodReturnType(node.getTypeDefinition(), methods, astArgumentList); + TypeNode enclosingType = getEnclosingTypeDeclaration(node); + if (enclosingType == null) { + return data; // we can't proceed, probably uncompiled sources + } + + previousType = getBestMethodReturnType(enclosingType.getTypeDefinition(), + methods, astArgumentList); } else { // field previousType = getTypeDefinitionOfVariableFromScope(node.getScope(), dotSplitImage[0], accessingClass); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java index a6a04fda1f..aac0250fe5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java @@ -4,6 +4,9 @@ package net.sourceforge.pmd.lang.java.typeresolution; +import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.LOOSE_INVOCATION; +import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE; + import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; @@ -12,7 +15,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTExpression; @@ -22,11 +24,10 @@ import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint; +import net.sourceforge.pmd.lang.java.typeresolution.typeinference.TypeInferenceResolver; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.TypeInferenceResolver.ResolutionFailedException; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable; -import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.LOOSE_INVOCATION; -import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE; public final class MethodTypeResolution { private MethodTypeResolution() {} @@ -134,7 +135,7 @@ public final class MethodTypeResolution { } } - methodType = parameterizeStrictInvocation(context, methodType.getMethod(), argList); + methodType = parameterizeInvocation(context, methodType.getMethod(), argList); } // check subtypeability of each argument to the corresponding parameter @@ -162,17 +163,18 @@ public final class MethodTypeResolution { } - public static MethodType parameterizeStrictInvocation(JavaTypeDefinition context, Method method, - ASTArgumentList argList) { + public static MethodType parameterizeInvocation(JavaTypeDefinition context, Method method, + ASTArgumentList argList) { // variables are set up by the call to produceInitialBounds List variables = new ArrayList<>(); List initialBounds = new ArrayList<>(); produceInitialBounds(method, context, variables, initialBounds); - List initialConstraints = produceInitialConstraints(method, argList, variables); + List resolvedTypeParameters = TypeInferenceResolver + .inferTypes(produceInitialConstraints(method, argList, variables), initialBounds, variables); - return null; + return getTypeDefOfMethod(context, method, resolvedTypeParameters); } public static List produceInitialConstraints(Method method, ASTArgumentList argList, @@ -184,14 +186,16 @@ public final class MethodTypeResolution { // TODO: add support for variable arity methods for (int i = 0; i < methodParameters.length; i++) { - int typeParamIndex; - if (methodParameters[i] instanceof TypeVariable - && (typeParamIndex = JavaTypeDefinition - .getGenericTypeIndex(methodTypeParameters, ((TypeVariable) methodParameters[i]).getName())) != -1) { + int typeParamIndex = -1; + if (methodParameters[i] instanceof TypeVariable) { + typeParamIndex = JavaTypeDefinition + .getGenericTypeIndex(methodTypeParameters, ((TypeVariable) methodParameters[i]).getName()); + } + if (typeParamIndex != -1) { // TODO: we are cheating here, it should be a contraint of the form 'var -> expression' not 'var->type' - result.add(new Constraint(variables.get(typeParamIndex), - ((TypeNode) argList.jjtGetChild(i)).getTypeDefinition(), LOOSE_INVOCATION)); + result.add(new Constraint(((TypeNode) argList.jjtGetChild(i)).getTypeDefinition(), + variables.get(typeParamIndex), LOOSE_INVOCATION)); } } @@ -222,10 +226,13 @@ public final class MethodTypeResolution { // appears in the set; if this results in no proper upper bounds for αl (only dependencies), then the // bound α <: Object also appears in the set. - int boundVarIndex; - if (bound instanceof TypeVariable - && (boundVarIndex = JavaTypeDefinition - .getGenericTypeIndex(typeVariables, ((TypeVariable) bound).getName())) != -1) { + int boundVarIndex = -1; + if (bound instanceof TypeVariable) { + boundVarIndex = + JavaTypeDefinition.getGenericTypeIndex(typeVariables, ((TypeVariable) bound).getName()); + } + + if (boundVarIndex != -1) { initialBounds.add(new Bound(variables.get(currVarIndex), variables.get(boundVarIndex), SUBTYPE)); } else { currVarHasNoProperUpperBound = false; @@ -436,13 +443,6 @@ public final class MethodTypeResolution { // search the class for (Method method : contextClass.getDeclaredMethods()) { if (isMethodApplicable(method, methodName, argArity, accessingClass, typeArguments)) { - if (isGeneric(method) && typeArguments.size() == 0) { - // TODO: do generic implicit methods - // this disables invocations which could match generic methods and have no explicit type args - result.clear(); - return result; - } - result.add(getTypeDefOfMethod(context, method, typeArguments)); } } @@ -496,7 +496,7 @@ public final class MethodTypeResolution { // if the method isn't vararg, then arity matches && (method.isVarArgs() || (argArity == getArity(method))) // isn't generic or arity of type arguments matches that of parameters - && (!isGeneric(method) || typeArguments == null + && (!isGeneric(method) || typeArguments.isEmpty() || method.getTypeParameters().length == typeArguments.size())) { return true; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java index 9cbddf8e7e..f3fed397d0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java @@ -23,6 +23,7 @@ import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefin public final class TypeInferenceResolver { + public static class ResolutionFailedException extends RuntimeException { } @@ -31,6 +32,38 @@ public final class TypeInferenceResolver { } + public static List inferTypes(List constraints, List bounds, + List variables) { + + List newBounds = new ArrayList<>(); + while (!constraints.isEmpty()) { + List reduceResult = constraints.get(constraints.size() - 1).reduce(); + constraints.remove(constraints.size() - 1); + + for (BoundOrConstraint boundOrConstraint : reduceResult) { + if (boundOrConstraint instanceof Bound) { + newBounds.add((Bound) boundOrConstraint); + } else if (boundOrConstraint instanceof Constraint) { + constraints.add((Constraint) boundOrConstraint); + } else { + throw new IllegalStateException("Unknown BoundOrConstraint subclass"); + } + } + + constraints.addAll(incorporateBounds(bounds, newBounds)); + newBounds.clear(); + } + + Map resolutionResult = resolveVariables(bounds); + List result = new ArrayList<>(); + + for (Variable variable : variables) { + result.add(resolutionResult.get(variable)); + } + + return result; + } + public static JavaTypeDefinition lub(List types) { for (JavaTypeDefinition type : types) { if (type.isArrayType()) { @@ -510,6 +543,8 @@ public final class TypeInferenceResolver { } } + currentBounds.addAll(newBounds); + return newConstraints; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java index c1cb77872b..c0eb14b4c4 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java @@ -23,15 +23,7 @@ import java.util.List; import java.util.Set; import java.util.StringTokenizer; -import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; -import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; -import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution; -import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound; -import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint; -import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable; -import net.sourceforge.pmd.typeresolution.testdata.GenericMethods; -import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther; -import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2; + import org.apache.commons.io.IOUtils; import org.jaxen.JaxenException; @@ -43,6 +35,7 @@ import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.JavaLanguageModule; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; @@ -62,11 +55,17 @@ import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; import net.sourceforge.pmd.lang.java.ast.AbstractJavaTypeNode; import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.java.typeresolution.ClassTypeResolver; +import net.sourceforge.pmd.lang.java.typeresolution.MethodType; +import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution; import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; +import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound; +import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint; +import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable; import net.sourceforge.pmd.typeresolution.testdata.AnonymousClassFromInterface; import net.sourceforge.pmd.typeresolution.testdata.AnonymousInnerClass; import net.sourceforge.pmd.typeresolution.testdata.AnoymousExtendingObject; @@ -85,6 +84,7 @@ import net.sourceforge.pmd.typeresolution.testdata.FieldAccessPrimaryGenericSimp import net.sourceforge.pmd.typeresolution.testdata.FieldAccessShadow; import net.sourceforge.pmd.typeresolution.testdata.FieldAccessStatic; import net.sourceforge.pmd.typeresolution.testdata.FieldAccessSuper; +import net.sourceforge.pmd.typeresolution.testdata.GenericMethodsImplicit; import net.sourceforge.pmd.typeresolution.testdata.InnerClass; import net.sourceforge.pmd.typeresolution.testdata.Literals; import net.sourceforge.pmd.typeresolution.testdata.MethodAccessibility; @@ -106,6 +106,8 @@ import net.sourceforge.pmd.typeresolution.testdata.dummytypes.JavaTypeDefinition import net.sourceforge.pmd.typeresolution.testdata.dummytypes.StaticMembers; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA2; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB2; @@ -1489,6 +1491,26 @@ public class ClassTypeResolverTest { assertEquals("All expressions not tested", index, expressions.size()); } + + @Test + public void testMethodTypeInference() throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class); + + List expressions = convertList( + acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), + AbstractJavaTypeNode.class); + + int index = 0; + + // SuperClassA2 a = bar((SuperClassA) null, (SuperClassAOther) null, null, (SuperClassAOther2) null); + assertEquals(SuperClassA2.class, expressions.get(index).getType()); + assertEquals(SuperClassA2.class, getChildType(expressions.get(index), 0)); + assertEquals(SuperClassA2.class, getChildType(expressions.get(index++), 1)); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + @Test public void testJavaTypeDefinitionEquals() { JavaTypeDefinition a = JavaTypeDefinition.forClass(Integer.class); @@ -1545,11 +1567,11 @@ public class ClassTypeResolverTest { @Test public void testMethodInitialBounds() throws NoSuchMethodException { - JavaTypeDefinition context = JavaTypeDefinition.forClass(GenericMethods.class, + JavaTypeDefinition context = JavaTypeDefinition.forClass(GenericMethodsImplicit.class, JavaTypeDefinition.forClass(Thread.class)); List variables = new ArrayList<>(); List initialBounds = new ArrayList<>(); - Method method = GenericMethods.class.getMethod("foo"); + Method method = GenericMethodsImplicit.class.getMethod("foo"); MethodTypeResolution.produceInitialBounds(method, context, variables, initialBounds); assertEquals(initialBounds.size(), 6); @@ -1572,38 +1594,61 @@ public class ClassTypeResolverTest { @Test public void testMethodInitialConstraints() throws NoSuchMethodException, JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethods.class); + ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class); List expressions = convertList( acu.findChildNodesWithXPath("//ArgumentList"), AbstractJavaNode.class); - assertEquals(expressions.size(), 1); - List variables = new ArrayList<>(); for (int i = 0; i < 2; ++i) { variables.add(new Variable()); } - Method method = GenericMethods.class.getMethod("bar", Object.class, Object.class, - Integer.class, Object.class); + Method method = GenericMethodsImplicit.class.getMethod("bar", Object.class, Object.class, + Integer.class, Object.class); ASTArgumentList argList = (ASTArgumentList) expressions.get(0); List constraints = MethodTypeResolution.produceInitialConstraints(method, argList, variables); assertEquals(constraints.size(), 3); // A - assertTrue(constraints.contains(new Constraint(variables.get(0), - JavaTypeDefinition.forClass(SuperClassA.class), - LOOSE_INVOCATION))); - assertTrue(constraints.contains(new Constraint(variables.get(0), - JavaTypeDefinition.forClass(SuperClassAOther.class), + assertTrue(constraints.contains(new Constraint(JavaTypeDefinition.forClass(SuperClassA.class), + variables.get(0), LOOSE_INVOCATION))); + assertTrue(constraints.contains(new Constraint(JavaTypeDefinition.forClass(SuperClassAOther.class), + variables.get(0), LOOSE_INVOCATION))); // B - assertTrue(constraints.contains(new Constraint(variables.get(1), - JavaTypeDefinition.forClass(SuperClassAOther2.class), + assertTrue(constraints.contains(new Constraint(JavaTypeDefinition.forClass(SuperClassAOther2.class), + variables.get(1), LOOSE_INVOCATION))); } + @Test + public void testMethodParameterization() throws JaxenException, NoSuchMethodException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class); + + List expressions = convertList( + acu.findChildNodesWithXPath("//ArgumentList"), + AbstractJavaNode.class); + + JavaTypeDefinition context = JavaTypeDefinition.forClass(GenericMethodsImplicit.class, + JavaTypeDefinition.forClass(Thread.class)); + Method method = GenericMethodsImplicit.class.getMethod("bar", Object.class, Object.class, + Integer.class, Object.class); + ASTArgumentList argList = (ASTArgumentList) expressions.get(0); + + MethodType inferedMethod = MethodTypeResolution.parameterizeInvocation(context, method, argList); + + assertEquals(inferedMethod.getParameterTypes().get(0), + JavaTypeDefinition.forClass(SuperClassA2.class)); + assertEquals(inferedMethod.getParameterTypes().get(1), + JavaTypeDefinition.forClass(SuperClassA2.class)); + assertEquals(inferedMethod.getParameterTypes().get(2), + JavaTypeDefinition.forClass(Integer.class)); + assertEquals(inferedMethod.getParameterTypes().get(3), + JavaTypeDefinition.forClass(SuperClassAOther2.class)); + } + private Class getChildType(Node node, int childIndex) { return ((TypeNode) node.jjtGetChild(childIndex)).getType(); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethods.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethods.java deleted file mode 100644 index a062bc76b0..0000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethods.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.sourceforge.pmd.typeresolution.testdata; - -import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA; -import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther; -import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2; - -public class GenericMethods { - public void foo() { - } - - public void bar(A a, A b, Integer c, B d) { - bar((SuperClassA) null, (SuperClassAOther) null, null, (SuperClassAOther2) null); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethodsImplicit.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethodsImplicit.java new file mode 100644 index 0000000000..b0256c1a56 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericMethodsImplicit.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA2; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2; + +public class GenericMethodsImplicit { + public void foo() { + } + + public A bar(A a, A b, Integer c, B d) { + return null; + } + + void test() { + SuperClassA2 a = bar((SuperClassA) null, (SuperClassAOther) null, (Integer) null, (SuperClassAOther2) null); + } +}