package org.jetbrains.plugins.ruby.ruby.codeInsight.types; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.ruby.ruby.codeInsight.resolve.ResolveUtil; import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.fqn.FQN; import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.Symbol; import org.jetbrains.plugins.ruby.ruby.codeInsight.symbols.structure.SymbolUtil; import org.jetbrains.plugins.ruby.ruby.codeInsight.types.impl.REmptyType; import org.jetbrains.plugins.ruby.ruby.lang.psi.RPossibleCall; import org.jetbrains.plugins.ruby.ruby.lang.psi.RPsiElement; import org.jetbrains.plugins.ruby.ruby.lang.psi.assoc.RAssoc; import org.jetbrains.plugins.ruby.ruby.lang.psi.expressions.RExpression; import org.jetbrains.plugins.ruby.ruby.lang.psi.methodCall.RArgumentToBlock; import org.jetbrains.plugins.ruby.ruby.lang.psi.methodCall.RCall; import org.jetbrains.plugins.ruby.ruby.lang.psi.references.RReference; import org.jetbrains.ruby.codeInsight.types.signature.ParameterInfo; import org.jetbrains.ruby.codeInsight.types.signature.RSignatureContract; import org.jetbrains.ruby.codeInsight.types.signature.RSignatureContractNode; import org.jetbrains.ruby.codeInsight.types.signature.RSignatureFetcher; import org.jetbrains.ruby.codeInsight.types.signature.contractTransition.ContractTransition; import org.jetbrains.ruby.codeInsight.types.signature.contractTransition.ReferenceContractTransition; import org.jetbrains.ruby.codeInsight.types.signature.contractTransition.TypedContractTransition; import org.jetbrains.ruby.runtime.signature.server.SignatureServer; import java.util.*; public class RubyStatTypeProviderImpl implements RubyStatTypeProvider { private int countMask(List<Set<String>> readTypes, String type, int currPosition) { int ans = 0; List<Set<String>> readTypesReversed = ContainerUtil.reverse(readTypes.subList(0, currPosition)); for (Set<String> strings : readTypesReversed) { ans <<= 1; if (strings.contains(type)) { ans |= 1; } } return ans; } private Map<RSignatureContractNode, List<Set<String>>> getNextLevel(Map<RSignatureContractNode, List<Set<String>>> currNodesAndReadTypes, Set<String> argTypeNames, int pos) { Map<RSignatureContractNode, List<Set<String>>> nextLayer = new HashMap<>(); currNodesAndReadTypes.forEach((node, readTypeSets) -> { for (final String typeName : argTypeNames) { TypedContractTransition typedTransition = new TypedContractTransition(typeName); if (node.containsKey(typedTransition)) { final List<Set<String>> newList = new ArrayList<>(readTypeSets); newList.add(ContainerUtil.newHashSet(typeName)); addReadTypesList(nextLayer, newList, node.goByTransition(typedTransition)); } else { int mask = countMask(readTypeSets, typeName, pos); ReferenceContractTransition refTransition = new ReferenceContractTransition(mask); if (node.containsKey(refTransition)) { final List<Set<String>> newList = new ArrayList<>(readTypeSets); newList.add(ContainerUtil.newHashSet(typeName)); addReadTypesList(nextLayer, newList, node.goByTransition(refTransition)); } } } }); return nextLayer; } private Map<RSignatureContractNode, List<Set<String>>> getNextLevelByString(Map<RSignatureContractNode, List<Set<String>>> currNodesAndReadTypes, String argName, int pos) { Set<String> tmpSet = new HashSet<>(); tmpSet.add(argName); return getNextLevel(currNodesAndReadTypes, tmpSet, pos); } public Symbol findMethodForType(@NotNull RType var1, @NotNull String var2) { return null; } public RType createTypeByCallAndArgs(@NotNull final RExpression call, @NotNull final List<RPsiElement> callArgs) { final PsiElement callElement = call instanceof RCall ? ((RCall) call).getPsiCommand() : call; SignatureServer callStatServer = SignatureServer.getInstance(); final Couple<String> names = getMethodAndReceiverNames(callElement); final String methodName = names.getFirst(); HashMap<String, RPsiElement> kwArgs = new HashMap<>(); LinkedList<RPsiElement> simpleArgs = new LinkedList<>(); for (RPsiElement arg : callArgs) { if (arg instanceof RAssoc) kwArgs.put(((RAssoc) arg).getKeyText(), arg); else simpleArgs.add(arg); } RSignatureFetcher fetcher = new RSignatureFetcher(callArgs.size(), kwArgs.keySet()); if (methodName != null) { final Module module = ModuleUtilCore.findModuleForPsiElement(call); final String receiverName = StringUtil.notNullize(names.getSecond(), CoreTypes.Object); RSignatureContract contract = callStatServer.getContractByMethodAndReceiverName(methodName, receiverName); if (contract == null) return REmptyType.INSTANCE; Map<RSignatureContractNode, List<Set<String>>> currNodesAndReadTypes = new HashMap<>(); currNodesAndReadTypes.put(contract.getStartNode(), new ArrayList<>()); if (contract != null && contract.locked == false) { List<ParameterInfo> paramInfos = contract.getParamInfoList(); List<Boolean> flags = fetcher.fetch(paramInfos); for (int i = 0; i < flags.size(); i++) { if (flags.get(i)) { if (paramInfos.get(i).getModifier() == ParameterInfo.Type.KEY || paramInfos.get(i).getModifier() == ParameterInfo.Type.KEYREQ) { RPsiElement currElement = kwArgs.get(paramInfos.get(i).getName()); currNodesAndReadTypes = getNextLevel(currNodesAndReadTypes, getArgTypeNames(currElement), i); } else { RPsiElement currElement = simpleArgs.poll(); currNodesAndReadTypes = getNextLevel(currNodesAndReadTypes, getArgTypeNames(currElement), i); } } else { currNodesAndReadTypes = getNextLevelByString(currNodesAndReadTypes, "-", i); } } } final Set<String> returnTypes = new HashSet<>(); currNodesAndReadTypes.forEach((node, readTypeSets) -> { for (final ContractTransition transition : node.getTransitionKeys()) { returnTypes.addAll(transition.getValue(readTypeSets)); } }); return returnTypes.stream() .map(name -> RTypeFactory.createTypeByFQN(call.getProject(), name)) .reduce(REmptyType.INSTANCE, RTypeUtil::union); } return REmptyType.INSTANCE; } private void addReadTypesList(Map<RSignatureContractNode, List<Set<String>>> nextLayer, List<Set<String>> readTypeSets, RSignatureContractNode to) { nextLayer.computeIfPresent(to, (rSignatureContractNode, sets) -> { for (int i = 0; i < sets.size(); i++) { sets.get(i).addAll(readTypeSets.get(i)); } return sets; }); nextLayer.putIfAbsent(to, readTypeSets); } @NotNull private static Couple<String> getMethodAndReceiverNames(@NotNull final PsiElement methodElement) { String methodName = null; String receiverFQN = null; final Symbol methodSymbol = ResolveUtil.resolveToSymbolWithCaching(methodElement.getReference(), false); if (methodSymbol != null) { final FQN methodFQN = methodSymbol.getFQNWithNesting(); methodName = methodFQN.getShortName(); receiverFQN = methodFQN.getCallerFQN().getFullPath(); return new Couple<>(methodName, receiverFQN); } else if (methodElement instanceof RPossibleCall) { methodName = ((RPossibleCall) methodElement).getName(); if (methodElement instanceof RReference) { final RPsiElement receiver = ((RReference) methodElement).getReceiver(); if (receiver instanceof RExpression) { receiverFQN = ((RExpression) receiver).getType().getName(); } } else { final Symbol receiverSymbol = SymbolUtil.getScopeContext(methodElement); if (receiverSymbol != null && SymbolUtil.isClassOrModuleSymbol(receiverSymbol.getType())) { receiverFQN = receiverSymbol.getFQNWithNesting().getFullPath(); } } } return new Couple<>(methodName, receiverFQN); } @NotNull private static Set<String> getArgTypeNames(@NotNull RPsiElement arg) { if (arg instanceof RAssoc) { arg = ((RAssoc) arg).getValue(); } final Set<String> argTypeNames = new HashSet<>(); if (arg instanceof RExpression) { RType type = ((RExpression) arg).getType(); if (type instanceof RUnionType) { final Deque<RType> types = new ArrayDeque<>(); types.add(type); while (!types.isEmpty()) { type = types.pop(); if (type instanceof RUnionType) { types.add(((RUnionType) type).getType1()); types.add(((RUnionType) type).getType2()); } else { argTypeNames.add(StringUtil.notNullize(type.getName(), CoreTypes.Object)); } } } else { argTypeNames.add(StringUtil.notNullize(type.getName(), CoreTypes.Object)); } } else if (arg instanceof RArgumentToBlock) { argTypeNames.add(CoreTypes.Proc); } return argTypeNames; } }