package me.tomassetti.turin.resolvers.compiled; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtMethod; import javassist.NotFoundException; import me.tomassetti.turin.compiler.AmbiguousCallException; import me.tomassetti.jvm.JvmType; import me.tomassetti.turin.resolvers.SymbolResolver; import me.tomassetti.turin.parser.ast.expressions.ActualParam; import me.tomassetti.turin.parser.ast.typeusage.TypeUsageNode; import me.tomassetti.turin.typesystem.ReferenceTypeUsage; import me.tomassetti.turin.typesystem.TypeUsage; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; public class JavassistBasedMethodResolution { private static class MethodOrConstructor { private CtConstructor constructor; private CtMethod method; public MethodOrConstructor(CtConstructor constructor) { this.constructor = constructor; } public MethodOrConstructor(CtMethod method) { this.method = method; } public int getParameterCount() throws NotFoundException { if (method != null) { return method.getParameterTypes().length; } else { return constructor.getParameterTypes().length; } } public CtClass getParameterType(int i) throws NotFoundException { if (method != null) { return method.getParameterTypes()[i]; } else { return constructor.getParameterTypes()[i]; } } } public static CtConstructor findConstructorAmong(List<JvmType> argsTypes, SymbolResolver resolver, List<CtConstructor> constructors) { try { List<MethodOrConstructor> methodOrConstructors = constructors.stream().map((m) -> new MethodOrConstructor(m)).collect(Collectors.toList()); MethodOrConstructor methodOrConstructor = findMethodAmong(argsTypes, resolver, methodOrConstructors, "constructor"); if (methodOrConstructor == null) { throw new RuntimeException("unresolved constructor for " + argsTypes); } return methodOrConstructor.constructor; } catch (NotFoundException e) { throw new RuntimeException(e); } } public static CtConstructor findConstructorAmongActualParams(List<ActualParam> argsTypes, SymbolResolver resolver, List<CtConstructor> constructors) { try { List<MethodOrConstructor> methodOrConstructors = constructors.stream().map((m) -> new MethodOrConstructor(m)).collect(Collectors.toList()); MethodOrConstructor methodOrConstructor = findMethodAmongActualParams(argsTypes, resolver, methodOrConstructors, "constructor"); if (methodOrConstructor == null) { throw new RuntimeException("unresolved constructor for " + argsTypes); } return methodOrConstructor.constructor; } catch (NotFoundException e) { throw new RuntimeException(e); } } public static CtMethod findMethodAmong(String name, List<JvmType> argsTypes, SymbolResolver resolver, boolean staticContext, List<CtMethod> methods) { try { List<MethodOrConstructor> methodOrConstructors = methods.stream() .filter((m) -> Modifier.isStatic(m.getModifiers()) == staticContext) .filter((m) -> m.getName().equals(name)) .map((m) -> new MethodOrConstructor(m)).collect(Collectors.toList()); MethodOrConstructor methodOrConstructor = findMethodAmong(argsTypes, resolver, methodOrConstructors, name); if (methodOrConstructor == null) { throw new RuntimeException("unresolved method " + name + " for " + argsTypes); } return methodOrConstructor.method; } catch (NotFoundException e) { throw new RuntimeException(e); } } public static Optional<CtMethod> findMethodAmongActualParams(String name, List<ActualParam> argsTypes, SymbolResolver resolver, boolean staticContext, List<CtMethod> methods) { try { List<MethodOrConstructor> methodOrConstructors = methods.stream() .filter((m) -> Modifier.isStatic(m.getModifiers()) == staticContext) .filter((m) -> m.getName().equals(name)) .map((m) -> new MethodOrConstructor(m)).collect(Collectors.toList()); MethodOrConstructor methodOrConstructor = findMethodAmongActualParams(argsTypes, resolver, methodOrConstructors, name); if (methodOrConstructor == null) { return Optional.empty(); } else { return Optional.of(methodOrConstructor.method); } } catch (NotFoundException e) { throw new RuntimeException(e); } } private static MethodOrConstructor findMethodAmong(List<JvmType> argsTypes, SymbolResolver resolver, List<MethodOrConstructor> methods, String desc) throws NotFoundException { List<MethodOrConstructor> suitableMethods = new ArrayList<>(); for (MethodOrConstructor method : methods) { if (method.getParameterCount() == argsTypes.size()) { boolean match = true; for (int i = 0; i < argsTypes.size(); i++) { TypeUsage actualType = TypeUsageNode.fromJvmType(argsTypes.get(i), resolver, Collections.emptyMap()); TypeUsage formalType = JavassistTypeDefinitionFactory.toTypeUsage(method.getParameterType(i), resolver); if (!actualType.canBeAssignedTo(formalType)) { match = false; } } if (match) { suitableMethods.add(method); } } } if (suitableMethods.size() == 0) { return null; } else if (suitableMethods.size() == 1) { return suitableMethods.get(0); } else { return findMostSpecific(suitableMethods, new AmbiguousCallException(null, desc, argsTypes), argsTypes, resolver); } } private static MethodOrConstructor findMethodAmongActualParams(List<ActualParam> argsTypes, SymbolResolver resolver, List<MethodOrConstructor> methods, String desc) throws NotFoundException { // TODO reorder params considering name List<MethodOrConstructor> suitableMethods = new ArrayList<>(); for (MethodOrConstructor method : methods) { if (method.getParameterCount() == argsTypes.size()) { boolean match = true; for (int i = 0; i < argsTypes.size(); i++) { TypeUsage actualType = argsTypes.get(i).getValue().calcType(); TypeUsage formalType = JavassistTypeDefinitionFactory.toTypeUsage(method.getParameterType(i), resolver); if (!actualType.canBeAssignedTo(formalType)) { match = false; } } if (match) { suitableMethods.add(method); } } } if (suitableMethods.size() == 0) { return null; } else if (suitableMethods.size() == 1) { return suitableMethods.get(0); } else { return findMostSpecific(suitableMethods, new AmbiguousCallException(null, argsTypes, desc), argsTypes.stream().map((ap)->ap.getValue().calcType().jvmType()).collect(Collectors.toList()), resolver); } } private static MethodOrConstructor findMostSpecific(List<MethodOrConstructor> methods, AmbiguousCallException exceptionToThrow, List<JvmType> argsTypes, SymbolResolver resolver) throws NotFoundException { MethodOrConstructor winningMethod = methods.get(0); for (MethodOrConstructor other : methods.subList(1, methods.size())) { if (isTheFirstMoreSpecific(winningMethod, other, argsTypes, resolver)) { } else if (isTheFirstMoreSpecific(other, winningMethod, argsTypes, resolver)) { winningMethod = other; } else if (!isTheFirstMoreSpecific(winningMethod, other, argsTypes, resolver)) { // neither is more specific throw exceptionToThrow; } } return winningMethod; } private static boolean isTheFirstMoreSpecific(MethodOrConstructor first, MethodOrConstructor second, List<JvmType> argsTypes, SymbolResolver resolver) throws NotFoundException { boolean atLeastOneParamIsMoreSpecific = false; if (first.getParameterCount() != second.getParameterCount()) { throw new IllegalArgumentException(); } for (int i=0;i<first.getParameterCount();i++){ CtClass paramFirst = first.getParameterType(i); CtClass paramSecond = second.getParameterType(i); if (isTheFirstMoreSpecific(paramFirst, paramSecond, argsTypes.get(i), resolver)) { atLeastOneParamIsMoreSpecific = true; } else if (isTheFirstMoreSpecific(paramSecond, paramFirst, argsTypes.get(i), resolver)) { return false; } } return atLeastOneParamIsMoreSpecific; } private static boolean isTheFirstMoreSpecific(CtClass firstType, CtClass secondType, JvmType targetType, SymbolResolver resolver) { boolean firstIsPrimitive = firstType.isPrimitive(); boolean secondIsPrimitive = secondType.isPrimitive(); boolean targetTypeIsPrimitive = targetType.isPrimitive(); // it is a match or a primitive promotion if (targetTypeIsPrimitive && firstIsPrimitive && !secondIsPrimitive) { return true; } if (targetTypeIsPrimitive && !firstIsPrimitive && secondIsPrimitive) { return false; } if (firstType.isPrimitive() || firstType.isArray()) { return false; } if (secondType.isPrimitive() || secondType.isArray()) { return false; } // TODO consider generic parameters? JavassistTypeDefinition firstDef = new JavassistTypeDefinition(firstType, resolver); JavassistTypeDefinition secondDef = new JavassistTypeDefinition(secondType, resolver); TypeUsage firstTypeUsage = new ReferenceTypeUsage(firstDef); TypeUsage secondTypeUsage = new ReferenceTypeUsage(secondDef); return firstTypeUsage.canBeAssignedTo(secondTypeUsage) && !secondTypeUsage.canBeAssignedTo(firstTypeUsage); } }