Use descriptors for building SAM constructors

It helps to get rid of semantics duplicating and fixes known bugs

- SOE in OnlyAbstractMethodFinder.find
- type enhancement for SAM constructors

 #KT-11287 Fixed
 #KT-11322 Fixed
 EA-77989 Fixed
This commit is contained in:
Denis Zharkov
2016-03-07 17:49:03 +03:00
parent b8b48c5f98
commit 151e55b2fa
22 changed files with 234 additions and 258 deletions
@@ -19,20 +19,13 @@ package org.jetbrains.kotlin.load.java.sam
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.load.java.components.SamConversionResolver
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaConstructorDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.load.java.descriptors.SamConstructorDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import java.util.*
object SamConversionResolverImpl : SamConversionResolver {
override fun resolveSamConstructor(constructorOwner: DeclarationDescriptor, classifier: () -> ClassifierDescriptor?): SamConstructorDescriptor? {
@@ -51,41 +44,8 @@ object SamConversionResolverImpl : SamConversionResolver {
}
}
override fun resolveFunctionTypeIfSamInterface(
classDescriptor: JavaClassDescriptor,
resolveMethod: (JavaMethod) -> FunctionDescriptor
): KotlinType? {
val jClass = (classDescriptor.source as? JavaSourceElement)?.javaElement as? JavaClass ?: return null
val samInterfaceMethod = SingleAbstractMethodUtils.getSamInterfaceMethod(jClass) ?: return null
val abstractMethod = if (jClass.fqName == samInterfaceMethod.containingClass.fqName) {
resolveMethod(samInterfaceMethod)
}
else {
findFunctionWithMostSpecificReturnType(TypeUtils.getAllSupertypes(classDescriptor.defaultType))
}
override fun resolveFunctionTypeIfSamInterface(classDescriptor: JavaClassDescriptor): KotlinType? {
val abstractMethod = SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) ?: return null
return SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(abstractMethod)
}
private fun findFunctionWithMostSpecificReturnType(supertypes: Set<KotlinType>): SimpleFunctionDescriptor {
val candidates = ArrayList<SimpleFunctionDescriptor>(supertypes.size)
for (supertype in supertypes) {
val abstractMembers = SingleAbstractMethodUtils.getAbstractMembers(supertype)
if (!abstractMembers.isEmpty()) {
candidates.add((abstractMembers[0] as SimpleFunctionDescriptor))
}
}
if (candidates.isEmpty()) {
throw IllegalStateException("Couldn't find abstract method in supertypes " + supertypes)
}
var currentMostSpecificType = candidates[0]
for (candidate in candidates) {
val candidateReturnType = candidate.returnType
val currentMostSpecificReturnType = currentMostSpecificType.returnType
assert(candidateReturnType != null && currentMostSpecificReturnType != null) { "$candidate, $currentMostSpecificReturnType" }
if (KotlinTypeChecker.DEFAULT.isSubtypeOf(candidateReturnType!!, currentMostSpecificReturnType!!)) {
currentMostSpecificType = candidate
}
}
return currentMostSpecificType
}
}
@@ -18,26 +18,23 @@ package org.jetbrains.kotlin.load.java.sam;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl;
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl;
import org.jetbrains.kotlin.load.java.components.DescriptorResolverUtils;
import org.jetbrains.kotlin.load.java.descriptors.*;
import org.jetbrains.kotlin.load.java.lazy.types.LazyJavaTypeResolver;
import org.jetbrains.kotlin.load.java.structure.*;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.calls.inference.CapturedTypeConstructorKt;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.jvm.JavaResolverUtils;
import org.jetbrains.kotlin.types.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.jetbrains.kotlin.types.Variance.INVARIANT;
import static org.jetbrains.kotlin.types.Variance.IN_VARIANCE;
public class SingleAbstractMethodUtils {
@@ -95,19 +92,23 @@ public class SingleAbstractMethodUtils {
return DescriptorUtilsKt.getBuiltIns(function).getFunctionType(Annotations.Companion.getEMPTY(), null, parameterTypes, returnType);
}
private static boolean isSamInterface(@NotNull ClassDescriptor klass) {
@Nullable
public static FunctionDescriptor getSingleAbstractMethodOrNull(@NotNull ClassDescriptor klass) {
if (klass.getKind() != ClassKind.INTERFACE) {
return false;
return null;
}
List<CallableMemberDescriptor> abstractMembers = getAbstractMembers(klass.getDefaultType());
if (abstractMembers.size() == 1) {
CallableMemberDescriptor member = abstractMembers.get(0);
if (member instanceof SimpleFunctionDescriptor) {
return member.getTypeParameters().isEmpty();
return member.getTypeParameters().isEmpty()
? (FunctionDescriptor) member
: null;
}
}
return false;
return null;
}
@NotNull
@@ -115,7 +116,7 @@ public class SingleAbstractMethodUtils {
@NotNull DeclarationDescriptor owner,
@NotNull JavaClassDescriptor samInterface
) {
assert isSamInterface(samInterface) : samInterface;
assert getSingleAbstractMethodOrNull(samInterface) != null : samInterface;
SamConstructorDescriptor result = new SamConstructorDescriptor(owner, samInterface);
@@ -282,29 +283,6 @@ public class SingleAbstractMethodUtils {
return new TypeParameters(typeParameters, typeParametersSubstitutor);
}
// Returns null if not SAM interface
@Nullable
public static JavaMethod getSamInterfaceMethod(@NotNull JavaClass javaClass) {
FqName fqName = javaClass.getFqName();
if (fqName == null || fqName.toUnsafe().startsWith(KotlinBuiltIns.BUILT_INS_PACKAGE_NAME)) {
return null;
}
if (!javaClass.isInterface() || javaClass.isAnnotationType()) {
return null;
}
return findOnlyAbstractMethod(javaClass);
}
@Nullable
private static JavaMethod findOnlyAbstractMethod(@NotNull JavaClass javaClass) {
OnlyAbstractMethodFinder finder = new OnlyAbstractMethodFinder();
if (finder.find(javaClass.getDefaultType())) {
return finder.getFoundMethod();
}
return null;
}
private static class TypeParameters {
public final List<TypeParameterDescriptor> descriptors;
public final TypeSubstitutor substitutor;
@@ -323,88 +301,5 @@ public class SingleAbstractMethodUtils {
);
}
private static class OnlyAbstractMethodFinder {
private static final FqName OBJECT_FQ_NAME = new FqName("java.lang.Object");
private JavaMethod foundMethod;
private JavaTypeSubstitutor foundClassSubstitutor;
private boolean find(@NotNull JavaClassifierType classifierType) {
JavaTypeSubstitutor classSubstitutor = classifierType.getSubstitutor();
JavaClassifier classifier = classifierType.getClassifier();
if (classifier == null) {
return false; // can't resolve class -> not a SAM interface
}
assert classifier instanceof JavaClass : "Classifier should be a class here: " + classifier;
JavaClass javaClass = (JavaClass) classifier;
if (OBJECT_FQ_NAME.equals(javaClass.getFqName())) {
return true;
}
for (JavaMethod method : javaClass.getMethods()) {
//skip java 8 default methods
if (!method.isAbstract()) {
continue;
}
if (DescriptorResolverUtils.isObjectMethod(method)) { // e.g., ignore toString() declared in interface
continue;
}
if (!method.getTypeParameters().isEmpty()) {
return false; // if interface has generic methods, it is not a SAM interface
}
if (foundMethod == null) {
foundMethod = method;
foundClassSubstitutor = classSubstitutor;
continue;
}
if (!areSignaturesErasureEqual(method, classSubstitutor, foundMethod, foundClassSubstitutor)) {
return false; // different signatures
}
}
for (JavaClassifierType t : classifierType.getSupertypes()) {
if (!find(t)) {
return false;
}
}
return true;
}
/**
* @see com.intellij.psi.util.MethodSignatureUtil#areSignaturesErasureEqual
*/
private static boolean areSignaturesErasureEqual(
@NotNull JavaMethod method1,
@NotNull JavaTypeSubstitutor substitutor1,
@NotNull JavaMethod method2,
@NotNull JavaTypeSubstitutor substitutor2
) {
if (!method1.getName().equals(method2.getName())) return false;
Collection<JavaValueParameter> parameters1 = method1.getValueParameters();
Collection<JavaValueParameter> parameters2 = method2.getValueParameters();
if (parameters1.size() != parameters2.size()) return false;
for (Iterator<JavaValueParameter> it1 = parameters1.iterator(), it2 = parameters2.iterator(); it1.hasNext(); ) {
JavaValueParameter param1 = it1.next();
JavaValueParameter param2 = it2.next();
if (param1.isVararg() != param2.isVararg()) return false;
JavaType type1 = JavaResolverUtils.erasure(substitutor1.substitute(param1.getType()), substitutor1);
JavaType type2 = JavaResolverUtils.erasure(substitutor2.substitute(param2.getType()), substitutor2);
if (!(type1 == null ? type2 == null : type1.equals(type2))) return false;
}
return true;
}
@Nullable
private JavaMethod getFoundMethod() {
return foundMethod;
}
}
}
@@ -34,81 +34,6 @@ public class JavaResolverUtils {
private JavaResolverUtils() {
}
/**
* @see com.intellij.psi.util.TypeConversionUtil#erasure(com.intellij.psi.PsiType)
*/
@Nullable
public static JavaType erasure(@NotNull JavaType type) {
return erasure(type, JavaTypeSubstitutor.EMPTY);
}
/**
* @see com.intellij.psi.util.TypeConversionUtil#erasure(com.intellij.psi.PsiType, com.intellij.psi.PsiSubstitutor)
*/
@Nullable
public static JavaType erasure(@NotNull JavaType type, @NotNull JavaTypeSubstitutor substitutor) {
if (type instanceof JavaClassifierType) {
JavaClassifier classifier = ((JavaClassifierType) type).getClassifier();
if (classifier instanceof JavaClass) {
return ((JavaClass) classifier).getDefaultType();
}
else if (classifier instanceof JavaTypeParameter) {
JavaTypeParameter typeParameter = (JavaTypeParameter) classifier;
return typeParameterErasure(typeParameter, new HashSet<JavaTypeParameter>(), substitutor);
}
else {
return null;
}
}
else if (type instanceof JavaPrimitiveType) {
return type;
}
else if (type instanceof JavaArrayType) {
JavaType erasure = erasure(((JavaArrayType) type).getComponentType(), substitutor);
return erasure == null ? null : erasure.createArrayType();
}
else if (type instanceof JavaWildcardType) {
JavaWildcardType wildcardType = (JavaWildcardType) type;
JavaType bound = wildcardType.getBound();
if (bound != null && wildcardType.isExtends()) {
return erasure(bound, substitutor);
}
return wildcardType.getTypeProvider().createJavaLangObjectType();
}
else {
throw new IllegalStateException("Unsupported type: " + type);
}
}
/**
* @see com.intellij.psi.util.TypeConversionUtil#typeParameterErasure(com.intellij.psi.PsiTypeParameter)
*/
@Nullable
private static JavaType typeParameterErasure(
@NotNull JavaTypeParameter typeParameter,
@NotNull HashSet<JavaTypeParameter> visited,
@NotNull JavaTypeSubstitutor substitutor
) {
Collection<JavaClassifierType> upperBounds = typeParameter.getUpperBounds();
if (!upperBounds.isEmpty()) {
JavaClassifier classifier = upperBounds.iterator().next().getClassifier();
if (classifier instanceof JavaTypeParameter && !visited.contains(classifier)) {
JavaTypeParameter typeParameterBound = (JavaTypeParameter) classifier;
visited.add(typeParameterBound);
JavaType substitutedType = substitutor.substitute(typeParameterBound);
if (substitutedType != null) {
return erasure(substitutedType);
}
return typeParameterErasure(typeParameterBound, visited, substitutor);
}
else if (classifier instanceof JavaClass) {
return ((JavaClass) classifier).getDefaultType();
}
}
return typeParameter.getTypeProvider().createJavaLangObjectType();
}
@NotNull
public static Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> recreateTypeParametersAndReturnMapping(
@NotNull List<TypeParameterDescriptor> originalParameters,
@Nullable DeclarationDescriptor newOwner