diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/CreateMethodFromUsageFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/CreateMethodFromUsageFix.java index 101da587ac4..afbf3c4835c 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/CreateMethodFromUsageFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/CreateMethodFromUsageFix.java @@ -82,6 +82,59 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { private static final String TEMPLATE_FROM_USAGE_METHOD_BODY = "New Kotlin Method Body.kt"; private static final Pattern COMPONENT_FUNCTION_PATTERN = Pattern.compile("^component(\\d+)$"); + /** + * Represents a single choice for a type (e.g. parameter type or return type). + */ + private static class TypeCandidate { + private final JetType type; + private final TypeParameterDescriptor[] typeParameters; + private String renderedType; + private String[] typeParameterNames; + + public TypeCandidate(@NotNull JetType type) { + this.type = type; + Set typeParametersInType = getTypeParametersInType(type); + typeParameters = typeParametersInType.toArray(new TypeParameterDescriptor[typeParametersInType.size()]); + render(Collections.emptyMap()); + } + + public TypeCandidate(@NotNull JetType type, @NotNull JetScope scope) { + this.type = type; + typeParameters = getTypeParameterNamesNotInScope(getTypeParametersInType(type), scope); + } + + public void render(@NotNull Map typeParameterNameMap) { + renderedType = renderTypeShort(type, typeParameterNameMap); + typeParameterNames = new String[typeParameters.length]; + int i = 0; + for (TypeParameterDescriptor typeParameter : typeParameters) { + typeParameterNames[i] = typeParameterNameMap.get(typeParameter); + i++; + } + } + + @NotNull JetType getType() { + return type; + } + + @NotNull + public String getRenderedType() { + assert renderedType != null : "call render() first"; + return renderedType; + } + + @NotNull + public String[] getTypeParameterNames() { + assert typeParameterNames != null : "call render() first"; + return typeParameterNames; + } + + @NotNull + public TypeParameterDescriptor[] getTypeParameters() { + return typeParameters; + } + } + /** * Represents a concrete type or a set of types yet to be inferred from an expression. */ @@ -89,7 +142,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { private final JetExpression expressionOfType; private final JetType type; private final Variance variance; - private JetType[] cachedTypeCandidates; + private TypeCandidate[] typeCandidates; private String[] cachedNameCandidatesFromExpression; public TypeOrExpressionThereof(@NotNull JetExpression expressionOfType) { @@ -120,17 +173,9 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { return this.type; } - /** - * Returns a collection containing the possible types represented by this instance. Infers the type from an expression if necessary. - * @return A collection containing the possible types represented by this instance. - */ @NotNull - public JetType[] getPossibleTypes(@Nullable("use cached, don't recompute") BindingContext context) { - if (context == null) { - assert cachedTypeCandidates != null; - return cachedTypeCandidates; - } - List types = new ArrayList(); + private Collection getPossibleTypes(BindingContext context) { + Collection types = new ArrayList(); if (isType()) { assert type != null : "!isType() means type == null && expressionOfType != null"; types.add(type); @@ -144,34 +189,28 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { } } } + return types; + } - if (types.isEmpty()) { - types.add(KotlinBuiltIns.getInstance().getAnyType()); + public void computeTypeCandidates(@NotNull BindingContext context) { + Collection types = getPossibleTypes(context); + + typeCandidates = new TypeCandidate[types.size()]; + int i = 0; + for (JetType type : types) { + typeCandidates[i] = new TypeCandidate(type); + i++; } - - return cachedTypeCandidates = types.toArray(new JetType[types.size()]); } - @NotNull - public JetType[] getPossibleTypes() { - return getPossibleTypes(null); - } + public void computeTypeCandidates( + @NotNull BindingContext context, + @NotNull TypeSubstitution[] substitutions, + @NotNull JetScope scope + ) { + Collection types = getPossibleTypes(context); - @NotNull - public String[] getPossibleNamesFromExpression() { - if (cachedNameCandidatesFromExpression != null) return cachedNameCandidatesFromExpression; - if (isType()) { - cachedNameCandidatesFromExpression = ArrayUtil.EMPTY_STRING_ARRAY; - } else { - assert expressionOfType != null : "!isType() means type == null && expressionOfType != null"; - JetNameValidator dummyValidator = JetNameValidator.getEmptyValidator(expressionOfType.getProject()); - cachedNameCandidatesFromExpression = JetNameSuggester.suggestNamesForExpression(expressionOfType, dummyValidator); - } - return cachedNameCandidatesFromExpression; - } - - public void substitute(TypeSubstitution[] substitutions) { - Set newTypes = new LinkedHashSet(Arrays.asList(cachedTypeCandidates)); + Set newTypes = new LinkedHashSet(types); for (TypeSubstitution substitution : substitutions) { // each substitution can be applied or not, so we offer all options List toAdd = new ArrayList(); List toRemove = new ArrayList(); @@ -185,7 +224,44 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { newTypes.addAll(toAdd); newTypes.removeAll(toRemove); } - cachedTypeCandidates = newTypes.toArray(new JetType[newTypes.size()]); + + if (newTypes.isEmpty()) { + newTypes.add(KotlinBuiltIns.getInstance().getAnyType()); + } + + types = newTypes; + + typeCandidates = new TypeCandidate[types.size()]; + int i = 0; + for (JetType type : types) { + typeCandidates[i] = new TypeCandidate(type, scope); + i++; + } + } + + @NotNull + public TypeCandidate[] getTypeCandidates() { + assert typeCandidates != null : "call computeTypeCandidates() first"; + return typeCandidates; + } + + public void renderTypeCandidates(@NotNull Map typeParameterNameMap) { + for (TypeCandidate candidate : typeCandidates) { + candidate.render(typeParameterNameMap); + } + } + + @NotNull + public String[] getPossibleNamesFromExpression() { + if (cachedNameCandidatesFromExpression != null) return cachedNameCandidatesFromExpression; + if (isType()) { + cachedNameCandidatesFromExpression = ArrayUtil.EMPTY_STRING_ARRAY; + } else { + assert expressionOfType != null : "!isType() means type == null && expressionOfType != null"; + JetNameValidator dummyValidator = JetNameValidator.getEmptyValidator(expressionOfType.getProject()); + cachedNameCandidatesFromExpression = JetNameSuggester.suggestNamesForExpression(expressionOfType, dummyValidator); + } + return cachedNameCandidatesFromExpression; } } @@ -298,17 +374,15 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { * An Expression for type references. */ private static class TypeExpression extends Expression { - private final JetType[] options; - private final String[] optionStrings; - private final LookupElement[] cachedLookupElements; + private final TypeOrExpressionThereof type; + private @NotNull LookupElement[] cachedLookupElements; - public TypeExpression(@NotNull JetType[] options) { - this.options = options; - optionStrings = new String[options.length]; - cachedLookupElements = new LookupElement[options.length]; - for (int i = 0; i < options.length; i++) { - optionStrings[i] = renderTypeShort(options[i]); - cachedLookupElements[i] = LookupElementBuilder.create(options[i], optionStrings[i]); + public TypeExpression(@NotNull TypeOrExpressionThereof type) { + this.type = type; + TypeCandidate[] candidates = type.getTypeCandidates(); + cachedLookupElements = new LookupElement[candidates.length]; + for (int i = 0; i < candidates.length; i++) { + cachedLookupElements[i] = LookupElementBuilder.create(candidates[i], candidates[i].getRenderedType()); } } @@ -334,20 +408,16 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { } @NotNull - public JetType[] getOptions() { - return options; - } - - @NotNull - public String[] getOptionStrings() { - return optionStrings; + public TypeOrExpressionThereof getType() { + return type; } @Nullable("can't be found") public JetType getTypeFromSelection(@NotNull String selection) { - for (int i = 0; i < options.length; i++) { - if (optionStrings[i].equals(selection)) { - return options[i]; + TypeCandidate[] options = type.getTypeCandidates(); + for (TypeCandidate option : options) { + if (option.getRenderedType().equals(selection)) { + return option.getType(); } } return null; @@ -358,13 +428,15 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { * A sort-of dummy Expression for parameter lists, to allow us to update the parameter list as the user makes selections. */ private static class TypeParameterListExpression extends Expression { - private final TypeParameterDescriptor[] typeParametersFromReceiverType; - private final Map typeParameterMap; + private final String[] typeParameterNamesFromReceiverType; + private final Map parameterTypeToTypeParameterNamesMap; - public TypeParameterListExpression(@NotNull TypeParameterDescriptor[] typeParametersFromReceiverType, - @NotNull Map typeParametersMap) { - this.typeParametersFromReceiverType = typeParametersFromReceiverType; - this.typeParameterMap = typeParametersMap; + public TypeParameterListExpression( + @NotNull String[] typeParameterNamesFromReceiverType, + @NotNull Map typeParametersMap + ) { + this.typeParameterNamesFromReceiverType = typeParameterNamesFromReceiverType; + this.parameterTypeToTypeParameterNamesMap = typeParametersMap; } @NotNull @@ -382,36 +454,24 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { assert func != null; List parameters = func.getValueParameters(); - Set typeParameters = new LinkedHashSet(); - Collections.addAll(typeParameters, typeParametersFromReceiverType); + Set typeParameterNames = new LinkedHashSet(); + Collections.addAll(typeParameterNames, typeParameterNamesFromReceiverType); for (JetParameter parameter : parameters) { JetTypeReference parameterTypeRef = parameter.getTypeReference(); assert parameterTypeRef != null; - TypeParameterDescriptor[] typeParametersFromParameter = typeParameterMap.get(parameterTypeRef.getText()); - if (typeParametersFromParameter != null) { - Collections.addAll(typeParameters, typeParametersFromParameter); + String[] typeParameterNamesFromParameter = parameterTypeToTypeParameterNamesMap.get(parameterTypeRef.getText()); + if (typeParameterNamesFromParameter != null) { + Collections.addAll(typeParameterNames, typeParameterNamesFromParameter); } } JetTypeReference returnTypeRef = func.getReturnTypeRef(); if (returnTypeRef != null) { - TypeParameterDescriptor[] typeParametersFromReturnType = typeParameterMap.get(returnTypeRef.getText()); - if (typeParametersFromReturnType != null) { - Collections.addAll(typeParameters, typeParametersFromReturnType); + String[] typeParameterNamesFromReturnType = parameterTypeToTypeParameterNamesMap.get(returnTypeRef.getText()); + if (typeParameterNamesFromReturnType != null) { + Collections.addAll(typeParameterNames, typeParameterNamesFromReturnType); } } - List typeParameterNames = new ArrayList(); - for (TypeParameterDescriptor typeParameter : typeParameters) { - typeParameterNames.add(typeParameter.getName().getIdentifier()); - } - - // make sure there are no name conflicts - for (int i = 0; i < typeParameterNames.size(); i++) { - String name = typeParameterNames.get(i); - name = getNextAvailableName(name, typeParameterNames.subList(0, i)); - typeParameterNames.set(i, name); - } - return typeParameterNames.isEmpty() ? new TextResult("") : new TextResult(" <" + StringUtil.join(typeParameterNames, ", ") + ">"); @@ -465,7 +525,8 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { private BindingContext currentFileContext; private JetClass ownerClass; private ClassDescriptor ownerClassDescriptor; - private JetType receiverType; + private TypeCandidate selectedReceiverType; + private Map typeParameterNameMap; public CreateMethodFromUsageFix(@NotNull PsiElement element, @NotNull TypeOrExpressionThereof ownerType, @NotNull String methodName, @NotNull TypeOrExpressionThereof returnType, @NotNull List parameters) { @@ -487,18 +548,20 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { assert file != null && file instanceof JetFile; // TODO: change some assertions to notifications currentFile = (JetFile) file; currentFileEditor = editor; + currentFileContext = AnalyzerFacadeWithCache.analyzeFileWithCache(currentFile).getBindingContext(); - JetType[] possibleOwnerTypes = ownerType.getPossibleTypes(); - assert possibleOwnerTypes.length > 0; - if (possibleOwnerTypes.length == 1) { - JetType ownerType = possibleOwnerTypes[0]; - doInvoke(project, ownerType); + ownerType.computeTypeCandidates(currentFileContext); + TypeCandidate[] ownerTypeCandidates = ownerType.getTypeCandidates(); + assert ownerTypeCandidates.length > 0; + if (ownerTypeCandidates.length == 1) { + selectedReceiverType = ownerTypeCandidates[0]; + doInvoke(project); } else { // class selection List options = new ArrayList(); - final Map optionToTypeMap = new HashMap(); - for (JetType possibleOwnerType : possibleOwnerTypes) { - ClassifierDescriptor possibleClassDescriptor = possibleOwnerType.getConstructor().getDeclarationDescriptor(); + final Map optionToTypeMap = new HashMap(); + for (TypeCandidate ownerTypeCandidate : ownerTypeCandidates) { + ClassifierDescriptor possibleClassDescriptor = ownerTypeCandidate.getType().getConstructor().getDeclarationDescriptor(); if (possibleClassDescriptor != null) { String className = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(possibleClassDescriptor.getDefaultType()); DeclarationDescriptor namespaceDescriptor = possibleClassDescriptor.getContainingDeclaration(); @@ -506,7 +569,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { String namespace = ((NamespaceDescriptor) namespaceDescriptor).getFqName().getFqName(); String option = className + " (" + namespace + ")"; options.add(option); - optionToTypeMap.put(option, possibleOwnerType); + optionToTypeMap.put(option, ownerTypeCandidate); } } @@ -523,11 +586,11 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { int index = list.getSelectedIndex(); if (index < 0) return; String option = (String) list.getSelectedValue(); - final JetType ownerType = optionToTypeMap.get(option); + selectedReceiverType = optionToTypeMap.get(option); CommandProcessor.getInstance().executeCommand(project, new Runnable() { @Override public void run() { - doInvoke(project, ownerType); + doInvoke(project); } }, getText(), null); } @@ -540,12 +603,12 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { } } - private void doInvoke(@NotNull final Project project, @NotNull JetType ownerType) { + private void doInvoke(@NotNull final Project project) { // gather relevant information - ClassifierDescriptor ownerTypeDescriptor = ownerType.getConstructor().getDeclarationDescriptor(); + ClassifierDescriptor ownerTypeDescriptor = selectedReceiverType.getType().getConstructor().getDeclarationDescriptor(); assert ownerTypeDescriptor != null && ownerTypeDescriptor instanceof ClassDescriptor; ownerClassDescriptor = (ClassDescriptor) ownerTypeDescriptor; - receiverType = ownerClassDescriptor.getDefaultType(); + JetType receiverType = ownerClassDescriptor.getDefaultType(); PsiElement typeDeclaration = BindingContextUtils.classDescriptorToDeclaration(currentFileContext, ownerClassDescriptor); if (typeDeclaration != null && typeDeclaration instanceof JetClass) { ownerClass = (JetClass) typeDeclaration; @@ -555,17 +618,41 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { } isUnit = returnType.isType() && isUnit(returnType.getType()); + JetScope scope; + if (isExtension) { + NamespaceDescriptor namespaceDescriptor = currentFileContext.get(BindingContext.FILE_TO_NAMESPACE, containingFile); + assert namespaceDescriptor != null; + scope = namespaceDescriptor.getMemberScope(); + } else { + assert ownerClassDescriptor instanceof MutableClassDescriptor; + scope = ((MutableClassDescriptor) ownerClassDescriptor).getScopeForMemberResolution(); + } + // figure out type substitutions for type parameters List classTypeParameters = receiverType.getArguments(); - List ownerTypeArguments = ownerType.getArguments(); + List ownerTypeArguments = selectedReceiverType.getType().getArguments(); assert ownerTypeArguments.size() == classTypeParameters.size(); TypeSubstitution[] substitutions = new TypeSubstitution[classTypeParameters.size()]; for (int i = 0; i < substitutions.length; i++) { substitutions[i] = new TypeSubstitution(ownerTypeArguments.get(i).getType(), classTypeParameters.get(i).getType()); } - returnType.substitute(substitutions); for (Parameter parameter : parameters) { - parameter.getType().substitute(substitutions); + parameter.getType().computeTypeCandidates(currentFileContext, substitutions, scope); + } + if (!isUnit) { + returnType.computeTypeCandidates(currentFileContext, substitutions, scope); + } + + // figure out type parameter renames to avoid conflicts + typeParameterNameMap = getTypeParameterRenames(scope); + for (Parameter parameter : parameters) { + parameter.getType().renderTypeCandidates(typeParameterNameMap); + } + if (!isUnit) { + returnType.renderTypeCandidates(typeParameterNameMap); + } + if (isExtension) { + ownerType.renderTypeCandidates(typeParameterNameMap); } ApplicationManager.getApplication().runWriteAction(new Runnable() { @@ -586,7 +673,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { String parametersString = StringUtil.join(parameterStrings,", "); String returnTypeString = isUnit ? "" : ": Any"; if (isExtension) { // create as extension function - String ownerTypeString = renderTypeShort(receiverType); + String ownerTypeString = selectedReceiverType.getRenderedType(); String methodText = String.format("fun %s.%s(%s)%s { }", ownerTypeString, methodName, parametersString, returnTypeString); func = JetPsiFactory.createFunction(project, methodText); containingFile = currentFile; @@ -619,16 +706,6 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { JetParameterList parameterList = func.getValueParameterList(); assert parameterList != null; - JetScope scope; - if (isExtension) { - NamespaceDescriptor namespaceDescriptor = currentFileContext.get(BindingContext.FILE_TO_NAMESPACE, containingFile); - assert namespaceDescriptor != null; - scope = namespaceDescriptor.getMemberScope(); - } else { - assert ownerClassDescriptor instanceof MutableClassDescriptor; - scope = ((MutableClassDescriptor) ownerClassDescriptor).getScopeForMemberResolution(); - } - // build templates PsiDocumentManager.getInstance(project).commitAllDocuments(); PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(containingFileEditor.getDocument()); @@ -645,8 +722,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { // need to create the segment first and then hack the Expression into the template later. We use this template to update the type // parameter list as the user makes selections in the parameter types, and we need alwaysStopAt to be false so the user can't tab to // it. - TypeParameterListExpression expression = - setupTypeParameterListTemplate(builder, func, parameterTypeExpressions, returnTypeExpression, scope); + TypeParameterListExpression expression = setupTypeParameterListTemplate(builder, func); // the template built by TemplateBuilderImpl is ordered by element position, but we want types to be first, so hack it final TemplateImpl template = (TemplateImpl) builder.buildInlineTemplate(); @@ -684,6 +760,39 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { }); } + private Map getTypeParameterRenames(JetScope scope) { + TypeParameterDescriptor[] receiverTypeParametersNotInScope = selectedReceiverType.getTypeParameters(); + Set allTypeParametersNotInScope = new LinkedHashSet(); + allTypeParametersNotInScope.addAll(Arrays.asList(receiverTypeParametersNotInScope)); + + for (Parameter parameter : parameters) { + TypeCandidate[] parameterTypeCandidates = parameter.getType().getTypeCandidates(); + for (TypeCandidate parameterTypeCandidate : parameterTypeCandidates) { + allTypeParametersNotInScope.addAll(Arrays.asList(parameterTypeCandidate.getTypeParameters())); + } + } + + if (!isUnit) { + TypeCandidate[] returnTypeCandidates = returnType.getTypeCandidates(); + for (TypeCandidate returnTypeCandidate : returnTypeCandidates) { + allTypeParametersNotInScope.addAll(Arrays.asList(returnTypeCandidate.getTypeParameters())); + } + } + + List typeParameters = new ArrayList(allTypeParametersNotInScope); + List typeParameterNames = new ArrayList(); + for (TypeParameterDescriptor typeParameter : typeParameters) { + typeParameterNames.add(getNextAvailableName(typeParameter.getName().getName(), typeParameterNames, scope)); + } + assert typeParameters.size() == typeParameterNames.size(); + Map typeParameterNameMap = new HashMap(); + for (int i = 0; i < typeParameters.size(); i++) { + typeParameterNameMap.put(typeParameters.get(i), typeParameterNames.get(i)); + } + + return typeParameterNameMap; + } + private void setupTypeReferencesForShortening( @NotNull Project project, @NotNull JetNamedFunction func, @@ -692,8 +801,9 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { @Nullable TypeExpression returnTypeExpression ) { if (isExtension) { - JetTypeReference receiverTypeRef = JetPsiFactory.createType(project, renderTypeLong(receiverType)); - replaceWithLongerName(project, receiverTypeRef, receiverType); + JetTypeReference receiverTypeRef = + JetPsiFactory.createType(project, renderTypeLong(selectedReceiverType.getType(), typeParameterNameMap)); + replaceWithLongerName(project, receiverTypeRef, selectedReceiverType.getType()); receiverTypeRef = func.getReceiverTypeRef(); assert receiverTypeRef != null; @@ -766,46 +876,33 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { private TypeExpression setupReturnTypeTemplate(@NotNull TemplateBuilder builder, @NotNull JetNamedFunction func) { JetTypeReference returnTypeRef = func.getReturnTypeRef(); assert returnTypeRef != null; - TypeExpression returnTypeExpression = new TypeExpression(returnType.getPossibleTypes()); + TypeExpression returnTypeExpression = new TypeExpression(returnType); builder.replaceElement(returnTypeRef, returnTypeExpression); return returnTypeExpression; } @NotNull - private TypeParameterListExpression setupTypeParameterListTemplate( - @NotNull TemplateBuilderImpl builder, - @NotNull JetNamedFunction func, - @NotNull TypeExpression[] parameterTypeExpressions, - @Nullable TypeExpression returnTypeExpression, - @NotNull JetScope scope - ) { - Map typeParameterMap = new HashMap(); - Set receiverTypeParameters = getTypeParametersInType(receiverType); - TypeParameterDescriptor[] receiverTypeParametersNotInScope = getTypeParameterNamesNotInScope(receiverTypeParameters, scope); - for (TypeExpression parameterTypeExpression : parameterTypeExpressions) { - JetType[] parameterTypeOptions = parameterTypeExpression.getOptions(); - String[] parameterTypeOptionStrings = parameterTypeExpression.getOptionStrings(); - assert parameterTypeOptions.length == parameterTypeOptionStrings.length; - for (int i = 0; i < parameterTypeOptions.length; i++) { - Set typeParameters = getTypeParametersInType(parameterTypeOptions[i]); - typeParameterMap.put(parameterTypeOptionStrings[i], getTypeParameterNamesNotInScope(typeParameters, scope)); + private TypeParameterListExpression setupTypeParameterListTemplate(@NotNull TemplateBuilderImpl builder, @NotNull JetNamedFunction func) { + Map typeParameterMap = new HashMap(); + String[] receiverTypeParameterNames = selectedReceiverType.getTypeParameterNames(); + + for (Parameter parameter : parameters) { + TypeCandidate[] parameterTypeCandidates = parameter.getType().getTypeCandidates(); + for (TypeCandidate parameterTypeCandidate : parameterTypeCandidates) { + typeParameterMap.put(parameterTypeCandidate.getRenderedType(), parameterTypeCandidate.getTypeParameterNames()); } } JetTypeReference returnTypeRef = func.getReturnTypeRef(); if (returnTypeRef != null) { - assert returnTypeExpression != null; - JetType[] returnTypeOptions = returnTypeExpression.getOptions(); - String[] returnTypeOptionStrings = returnTypeExpression.getOptionStrings(); - assert returnTypeOptions.length == returnTypeOptionStrings.length; - for (int i = 0; i < returnTypeOptions.length; i++) { - Set typeParameters = getTypeParametersInType(returnTypeOptions[i]); - typeParameterMap.put(returnTypeOptionStrings[i], getTypeParameterNamesNotInScope(typeParameters, scope)); + TypeCandidate[] returnTypeCandidates = returnType.getTypeCandidates(); + for (TypeCandidate returnTypeCandidate : returnTypeCandidates) { + typeParameterMap.put(returnTypeCandidate.getRenderedType(), returnTypeCandidate.getTypeParameterNames()); } } builder.replaceElement(func, TextRange.create(3, 3), TYPE_PARAMETER_LIST_VARIABLE_NAME, null, false); // ((3, 3) is after "fun") - return new TypeParameterListExpression(receiverTypeParametersNotInScope, typeParameterMap); + return new TypeParameterListExpression(receiverTypeParameterNames, typeParameterMap); } private TypeExpression[] setupParameterTypeTemplates(@NotNull Project project, @NotNull TemplateBuilder builder, @@ -813,13 +910,13 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { List jetParameters = parameterList.getParameters(); assert jetParameters.size() == parameters.size(); TypeExpression[] parameterTypeExpressions = new TypeExpression[parameters.size()]; + JetNameValidator dummyValidator = JetNameValidator.getEmptyValidator(project); for (int i = 0; i < parameters.size(); i++) { Parameter parameter = parameters.get(i); JetParameter jetParameter = jetParameters.get(i); // add parameter type to the template - JetType[] typeOptions = parameter.getType().getPossibleTypes(); - parameterTypeExpressions[i] = new TypeExpression(typeOptions); + parameterTypeExpressions[i] = new TypeExpression(parameter.getType()); JetTypeReference parameterTypeRef = jetParameter.getTypeReference(); assert parameterTypeRef != null; builder.replaceElement(parameterTypeRef, parameterTypeExpressions[i]); @@ -838,11 +935,9 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { // figure out suggested names for each type option Map parameterTypeToNamesMap = new HashMap(); - String[] typeOptionStrings = parameterTypeExpressions[i].getOptionStrings(); - assert typeOptions.length == typeOptionStrings.length; - for (int j = 0; j < typeOptions.length; j++) { - String[] suggestedNames = JetNameSuggester.suggestNamesForType(typeOptions[j], JetNameValidator.getEmptyValidator(project)); - parameterTypeToNamesMap.put(typeOptionStrings[j], suggestedNames); + for (TypeCandidate typeCandidate : parameter.getType().getTypeCandidates()) { + String[] suggestedNames = JetNameSuggester.suggestNamesForType(typeCandidate.getType(), dummyValidator); + parameterTypeToNamesMap.put(typeCandidate.getRenderedType(), suggestedNames); } // add expression to builder @@ -854,31 +949,8 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { return parameterTypeExpressions; } - @Override - public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - assert file instanceof JetFile; - JetFile jetFile = (JetFile) file; - currentFileContext = AnalyzerFacadeWithCache.analyzeFileWithCache(jetFile).getBindingContext(); - - JetType[] possibleOwnerTypes = ownerType.getPossibleTypes(currentFileContext); - if (possibleOwnerTypes.length == 0) return false; - assertNoUnitTypes(possibleOwnerTypes); - JetType[] possibleReturnTypes = returnType.getPossibleTypes(currentFileContext); - if (!returnType.isType()) { // allow return type to be unit (but not when it's among several options) - assertNoUnitTypes(possibleReturnTypes); - } - if (possibleReturnTypes.length == 0) return false; - for (Parameter parameter : parameters) { - JetType[] possibleTypes = parameter.getType().getPossibleTypes(currentFileContext); - if (possibleTypes.length == 0) return false; - assertNoUnitTypes(possibleTypes); - } - - return true; - } - - private static void replaceWithLongerName(@NotNull Project project, @NotNull JetTypeReference typeRef, @NotNull JetType type) { - JetTypeReference fullyQualifiedReceiverTypeRef = JetPsiFactory.createType(project, renderTypeLong(type)); + private void replaceWithLongerName(@NotNull Project project, @NotNull JetTypeReference typeRef, @NotNull JetType type) { + JetTypeReference fullyQualifiedReceiverTypeRef = JetPsiFactory.createType(project, renderTypeLong(type, typeParameterNameMap)); typeRef.replace(fullyQualifiedReceiverTypeRef); } @@ -933,13 +1005,45 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { } @NotNull - private static String renderTypeShort(@NotNull JetType type) { - return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(type); + private static String renderDescriptor( + @NotNull DeclarationDescriptor declarationDescriptor, + @NotNull Map typeParameterNameMap, + boolean fq + ) { + if (declarationDescriptor instanceof TypeParameterDescriptor) { + String replacement = typeParameterNameMap.get(declarationDescriptor); + return replacement == null ? declarationDescriptor.getName().getName() : replacement; + } else if (declarationDescriptor instanceof ClassDescriptor) { + return fq ? DescriptorUtils.getFQName(declarationDescriptor).getFqName() : declarationDescriptor.getName().getName(); + } else { + assert false : "unexpected descriptor in type"; + return null; + } + } + + private static String renderType(@NotNull JetType type, @NotNull Map typeParameterNameMap, boolean fq) { + List projections = type.getArguments(); + DeclarationDescriptor declarationDescriptor = type.getConstructor().getDeclarationDescriptor(); + assert declarationDescriptor != null; + if (projections.isEmpty()) { + return renderDescriptor(declarationDescriptor, typeParameterNameMap, fq); + } + + List arguments = new ArrayList(); + for (TypeProjection projection : projections) { + arguments.add(renderTypeLong(projection.getType(), typeParameterNameMap)); + } + return renderDescriptor(declarationDescriptor, typeParameterNameMap, fq) + "<" + StringUtil.join(arguments, ", ") + ">"; } @NotNull - private static String renderTypeLong(@NotNull JetType type) { - return DescriptorRenderer.TEXT.renderType(type); + private static String renderTypeShort(@NotNull JetType type, @NotNull Map typeParameterNameMap) { + return renderType(type, typeParameterNameMap, false); + } + + @NotNull + private static String renderTypeLong(@NotNull JetType type, @NotNull Map typeParameterNameMap) { + return renderType(type, typeParameterNameMap, true); } @NotNull @@ -984,6 +1088,16 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { return name; } + @NotNull + private static String getNextAvailableName(@NotNull String name, @NotNull Collection existingNames, @NotNull JetScope scope) { + if (existingNames.contains(name) || scope.getClassifier(Name.identifier(name)) != null) { + int j = 1; + while (existingNames.contains(name + j) || scope.getClassifier(Name.identifier(name + j)) != null) j++; + name += j; + } + return name; + } + @NotNull private static JetType[] guessTypeForExpression(@NotNull JetExpression expr, @NotNull BindingContext context) { JetType type = context.get(BindingContext.EXPRESSION_TYPE, expr); @@ -1070,12 +1184,6 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase { return KotlinBuiltIns.getInstance().isUnit(type); } - private static void assertNoUnitTypes(@NotNull JetType[] types) { - for (JetType type : types) { - assert !isUnit(type); - } - } - @NotNull public static JetIntentionActionFactory createCreateGetMethodFromUsageFactory() { return new JetIntentionActionFactory() {