Create from usage: Added type parameter renaming.

This commit is contained in:
Jack Zhou
2013-04-20 19:06:14 -04:00
parent fc6c2be73b
commit a80ea6904a
@@ -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 String TEMPLATE_FROM_USAGE_METHOD_BODY = "New Kotlin Method Body.kt";
private static final Pattern COMPONENT_FUNCTION_PATTERN = Pattern.compile("^component(\\d+)$"); 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<TypeParameterDescriptor> typeParametersInType = getTypeParametersInType(type);
typeParameters = typeParametersInType.toArray(new TypeParameterDescriptor[typeParametersInType.size()]);
render(Collections.<TypeParameterDescriptor, String>emptyMap());
}
public TypeCandidate(@NotNull JetType type, @NotNull JetScope scope) {
this.type = type;
typeParameters = getTypeParameterNamesNotInScope(getTypeParametersInType(type), scope);
}
public void render(@NotNull Map<TypeParameterDescriptor, String> 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. * 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 JetExpression expressionOfType;
private final JetType type; private final JetType type;
private final Variance variance; private final Variance variance;
private JetType[] cachedTypeCandidates; private TypeCandidate[] typeCandidates;
private String[] cachedNameCandidatesFromExpression; private String[] cachedNameCandidatesFromExpression;
public TypeOrExpressionThereof(@NotNull JetExpression expressionOfType) { public TypeOrExpressionThereof(@NotNull JetExpression expressionOfType) {
@@ -120,17 +173,9 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
return this.type; 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 @NotNull
public JetType[] getPossibleTypes(@Nullable("use cached, don't recompute") BindingContext context) { private Collection<JetType> getPossibleTypes(BindingContext context) {
if (context == null) { Collection<JetType> types = new ArrayList<JetType>();
assert cachedTypeCandidates != null;
return cachedTypeCandidates;
}
List<JetType> types = new ArrayList<JetType>();
if (isType()) { if (isType()) {
assert type != null : "!isType() means type == null && expressionOfType != null"; assert type != null : "!isType() means type == null && expressionOfType != null";
types.add(type); types.add(type);
@@ -144,34 +189,28 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
} }
} }
} }
return types;
}
if (types.isEmpty()) { public void computeTypeCandidates(@NotNull BindingContext context) {
types.add(KotlinBuiltIns.getInstance().getAnyType()); Collection<JetType> 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 void computeTypeCandidates(
public JetType[] getPossibleTypes() { @NotNull BindingContext context,
return getPossibleTypes(null); @NotNull TypeSubstitution[] substitutions,
} @NotNull JetScope scope
) {
Collection<JetType> types = getPossibleTypes(context);
@NotNull Set<JetType> newTypes = new LinkedHashSet<JetType>(types);
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<JetType> newTypes = new LinkedHashSet<JetType>(Arrays.asList(cachedTypeCandidates));
for (TypeSubstitution substitution : substitutions) { // each substitution can be applied or not, so we offer all options for (TypeSubstitution substitution : substitutions) { // each substitution can be applied or not, so we offer all options
List<JetType> toAdd = new ArrayList<JetType>(); List<JetType> toAdd = new ArrayList<JetType>();
List<JetType> toRemove = new ArrayList<JetType>(); List<JetType> toRemove = new ArrayList<JetType>();
@@ -185,7 +224,44 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
newTypes.addAll(toAdd); newTypes.addAll(toAdd);
newTypes.removeAll(toRemove); 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<TypeParameterDescriptor, String> 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 <code>Expression</code> for type references. * An <code>Expression</code> for type references.
*/ */
private static class TypeExpression extends Expression { private static class TypeExpression extends Expression {
private final JetType[] options; private final TypeOrExpressionThereof type;
private final String[] optionStrings; private @NotNull LookupElement[] cachedLookupElements;
private final LookupElement[] cachedLookupElements;
public TypeExpression(@NotNull JetType[] options) { public TypeExpression(@NotNull TypeOrExpressionThereof type) {
this.options = options; this.type = type;
optionStrings = new String[options.length]; TypeCandidate[] candidates = type.getTypeCandidates();
cachedLookupElements = new LookupElement[options.length]; cachedLookupElements = new LookupElement[candidates.length];
for (int i = 0; i < options.length; i++) { for (int i = 0; i < candidates.length; i++) {
optionStrings[i] = renderTypeShort(options[i]); cachedLookupElements[i] = LookupElementBuilder.create(candidates[i], candidates[i].getRenderedType());
cachedLookupElements[i] = LookupElementBuilder.create(options[i], optionStrings[i]);
} }
} }
@@ -334,20 +408,16 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
} }
@NotNull @NotNull
public JetType[] getOptions() { public TypeOrExpressionThereof getType() {
return options; return type;
}
@NotNull
public String[] getOptionStrings() {
return optionStrings;
} }
@Nullable("can't be found") @Nullable("can't be found")
public JetType getTypeFromSelection(@NotNull String selection) { public JetType getTypeFromSelection(@NotNull String selection) {
for (int i = 0; i < options.length; i++) { TypeCandidate[] options = type.getTypeCandidates();
if (optionStrings[i].equals(selection)) { for (TypeCandidate option : options) {
return options[i]; if (option.getRenderedType().equals(selection)) {
return option.getType();
} }
} }
return null; return null;
@@ -358,13 +428,15 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
* A sort-of dummy <code>Expression</code> for parameter lists, to allow us to update the parameter list as the user makes selections. * A sort-of dummy <code>Expression</code> for parameter lists, to allow us to update the parameter list as the user makes selections.
*/ */
private static class TypeParameterListExpression extends Expression { private static class TypeParameterListExpression extends Expression {
private final TypeParameterDescriptor[] typeParametersFromReceiverType; private final String[] typeParameterNamesFromReceiverType;
private final Map<String, TypeParameterDescriptor[]> typeParameterMap; private final Map<String, String[]> parameterTypeToTypeParameterNamesMap;
public TypeParameterListExpression(@NotNull TypeParameterDescriptor[] typeParametersFromReceiverType, public TypeParameterListExpression(
@NotNull Map<String, TypeParameterDescriptor[]> typeParametersMap) { @NotNull String[] typeParameterNamesFromReceiverType,
this.typeParametersFromReceiverType = typeParametersFromReceiverType; @NotNull Map<String, String[]> typeParametersMap
this.typeParameterMap = typeParametersMap; ) {
this.typeParameterNamesFromReceiverType = typeParameterNamesFromReceiverType;
this.parameterTypeToTypeParameterNamesMap = typeParametersMap;
} }
@NotNull @NotNull
@@ -382,36 +454,24 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
assert func != null; assert func != null;
List<JetParameter> parameters = func.getValueParameters(); List<JetParameter> parameters = func.getValueParameters();
Set<TypeParameterDescriptor> typeParameters = new LinkedHashSet<TypeParameterDescriptor>(); Set<String> typeParameterNames = new LinkedHashSet<String>();
Collections.addAll(typeParameters, typeParametersFromReceiverType); Collections.addAll(typeParameterNames, typeParameterNamesFromReceiverType);
for (JetParameter parameter : parameters) { for (JetParameter parameter : parameters) {
JetTypeReference parameterTypeRef = parameter.getTypeReference(); JetTypeReference parameterTypeRef = parameter.getTypeReference();
assert parameterTypeRef != null; assert parameterTypeRef != null;
TypeParameterDescriptor[] typeParametersFromParameter = typeParameterMap.get(parameterTypeRef.getText()); String[] typeParameterNamesFromParameter = parameterTypeToTypeParameterNamesMap.get(parameterTypeRef.getText());
if (typeParametersFromParameter != null) { if (typeParameterNamesFromParameter != null) {
Collections.addAll(typeParameters, typeParametersFromParameter); Collections.addAll(typeParameterNames, typeParameterNamesFromParameter);
} }
} }
JetTypeReference returnTypeRef = func.getReturnTypeRef(); JetTypeReference returnTypeRef = func.getReturnTypeRef();
if (returnTypeRef != null) { if (returnTypeRef != null) {
TypeParameterDescriptor[] typeParametersFromReturnType = typeParameterMap.get(returnTypeRef.getText()); String[] typeParameterNamesFromReturnType = parameterTypeToTypeParameterNamesMap.get(returnTypeRef.getText());
if (typeParametersFromReturnType != null) { if (typeParameterNamesFromReturnType != null) {
Collections.addAll(typeParameters, typeParametersFromReturnType); Collections.addAll(typeParameterNames, typeParameterNamesFromReturnType);
} }
} }
List<String> typeParameterNames = new ArrayList<String>();
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() return typeParameterNames.isEmpty()
? new TextResult("") ? new TextResult("")
: new TextResult(" <" + StringUtil.join(typeParameterNames, ", ") + ">"); : new TextResult(" <" + StringUtil.join(typeParameterNames, ", ") + ">");
@@ -465,7 +525,8 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
private BindingContext currentFileContext; private BindingContext currentFileContext;
private JetClass ownerClass; private JetClass ownerClass;
private ClassDescriptor ownerClassDescriptor; private ClassDescriptor ownerClassDescriptor;
private JetType receiverType; private TypeCandidate selectedReceiverType;
private Map<TypeParameterDescriptor, String> typeParameterNameMap;
public CreateMethodFromUsageFix(@NotNull PsiElement element, @NotNull TypeOrExpressionThereof ownerType, @NotNull String methodName, public CreateMethodFromUsageFix(@NotNull PsiElement element, @NotNull TypeOrExpressionThereof ownerType, @NotNull String methodName,
@NotNull TypeOrExpressionThereof returnType, @NotNull List<Parameter> parameters) { @NotNull TypeOrExpressionThereof returnType, @NotNull List<Parameter> parameters) {
@@ -487,18 +548,20 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
assert file != null && file instanceof JetFile; // TODO: change some assertions to notifications assert file != null && file instanceof JetFile; // TODO: change some assertions to notifications
currentFile = (JetFile) file; currentFile = (JetFile) file;
currentFileEditor = editor; currentFileEditor = editor;
currentFileContext = AnalyzerFacadeWithCache.analyzeFileWithCache(currentFile).getBindingContext();
JetType[] possibleOwnerTypes = ownerType.getPossibleTypes(); ownerType.computeTypeCandidates(currentFileContext);
assert possibleOwnerTypes.length > 0; TypeCandidate[] ownerTypeCandidates = ownerType.getTypeCandidates();
if (possibleOwnerTypes.length == 1) { assert ownerTypeCandidates.length > 0;
JetType ownerType = possibleOwnerTypes[0]; if (ownerTypeCandidates.length == 1) {
doInvoke(project, ownerType); selectedReceiverType = ownerTypeCandidates[0];
doInvoke(project);
} else { } else {
// class selection // class selection
List<String> options = new ArrayList<String>(); List<String> options = new ArrayList<String>();
final Map<String, JetType> optionToTypeMap = new HashMap<String, JetType>(); final Map<String, TypeCandidate> optionToTypeMap = new HashMap<String, TypeCandidate>();
for (JetType possibleOwnerType : possibleOwnerTypes) { for (TypeCandidate ownerTypeCandidate : ownerTypeCandidates) {
ClassifierDescriptor possibleClassDescriptor = possibleOwnerType.getConstructor().getDeclarationDescriptor(); ClassifierDescriptor possibleClassDescriptor = ownerTypeCandidate.getType().getConstructor().getDeclarationDescriptor();
if (possibleClassDescriptor != null) { if (possibleClassDescriptor != null) {
String className = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(possibleClassDescriptor.getDefaultType()); String className = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(possibleClassDescriptor.getDefaultType());
DeclarationDescriptor namespaceDescriptor = possibleClassDescriptor.getContainingDeclaration(); DeclarationDescriptor namespaceDescriptor = possibleClassDescriptor.getContainingDeclaration();
@@ -506,7 +569,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
String namespace = ((NamespaceDescriptor) namespaceDescriptor).getFqName().getFqName(); String namespace = ((NamespaceDescriptor) namespaceDescriptor).getFqName().getFqName();
String option = className + " (" + namespace + ")"; String option = className + " (" + namespace + ")";
options.add(option); options.add(option);
optionToTypeMap.put(option, possibleOwnerType); optionToTypeMap.put(option, ownerTypeCandidate);
} }
} }
@@ -523,11 +586,11 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
int index = list.getSelectedIndex(); int index = list.getSelectedIndex();
if (index < 0) return; if (index < 0) return;
String option = (String) list.getSelectedValue(); String option = (String) list.getSelectedValue();
final JetType ownerType = optionToTypeMap.get(option); selectedReceiverType = optionToTypeMap.get(option);
CommandProcessor.getInstance().executeCommand(project, new Runnable() { CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override @Override
public void run() { public void run() {
doInvoke(project, ownerType); doInvoke(project);
} }
}, getText(), null); }, 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 // gather relevant information
ClassifierDescriptor ownerTypeDescriptor = ownerType.getConstructor().getDeclarationDescriptor(); ClassifierDescriptor ownerTypeDescriptor = selectedReceiverType.getType().getConstructor().getDeclarationDescriptor();
assert ownerTypeDescriptor != null && ownerTypeDescriptor instanceof ClassDescriptor; assert ownerTypeDescriptor != null && ownerTypeDescriptor instanceof ClassDescriptor;
ownerClassDescriptor = (ClassDescriptor) ownerTypeDescriptor; ownerClassDescriptor = (ClassDescriptor) ownerTypeDescriptor;
receiverType = ownerClassDescriptor.getDefaultType(); JetType receiverType = ownerClassDescriptor.getDefaultType();
PsiElement typeDeclaration = BindingContextUtils.classDescriptorToDeclaration(currentFileContext, ownerClassDescriptor); PsiElement typeDeclaration = BindingContextUtils.classDescriptorToDeclaration(currentFileContext, ownerClassDescriptor);
if (typeDeclaration != null && typeDeclaration instanceof JetClass) { if (typeDeclaration != null && typeDeclaration instanceof JetClass) {
ownerClass = (JetClass) typeDeclaration; ownerClass = (JetClass) typeDeclaration;
@@ -555,17 +618,41 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
} }
isUnit = returnType.isType() && isUnit(returnType.getType()); 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 // figure out type substitutions for type parameters
List<TypeProjection> classTypeParameters = receiverType.getArguments(); List<TypeProjection> classTypeParameters = receiverType.getArguments();
List<TypeProjection> ownerTypeArguments = ownerType.getArguments(); List<TypeProjection> ownerTypeArguments = selectedReceiverType.getType().getArguments();
assert ownerTypeArguments.size() == classTypeParameters.size(); assert ownerTypeArguments.size() == classTypeParameters.size();
TypeSubstitution[] substitutions = new TypeSubstitution[classTypeParameters.size()]; TypeSubstitution[] substitutions = new TypeSubstitution[classTypeParameters.size()];
for (int i = 0; i < substitutions.length; i++) { for (int i = 0; i < substitutions.length; i++) {
substitutions[i] = new TypeSubstitution(ownerTypeArguments.get(i).getType(), classTypeParameters.get(i).getType()); substitutions[i] = new TypeSubstitution(ownerTypeArguments.get(i).getType(), classTypeParameters.get(i).getType());
} }
returnType.substitute(substitutions);
for (Parameter parameter : parameters) { 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() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -586,7 +673,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
String parametersString = StringUtil.join(parameterStrings,", "); String parametersString = StringUtil.join(parameterStrings,", ");
String returnTypeString = isUnit ? "" : ": Any"; String returnTypeString = isUnit ? "" : ": Any";
if (isExtension) { // create as extension function 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); String methodText = String.format("fun %s.%s(%s)%s { }", ownerTypeString, methodName, parametersString, returnTypeString);
func = JetPsiFactory.createFunction(project, methodText); func = JetPsiFactory.createFunction(project, methodText);
containingFile = currentFile; containingFile = currentFile;
@@ -619,16 +706,6 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
JetParameterList parameterList = func.getValueParameterList(); JetParameterList parameterList = func.getValueParameterList();
assert parameterList != null; 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 // build templates
PsiDocumentManager.getInstance(project).commitAllDocuments(); PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(containingFileEditor.getDocument()); 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 // 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 // 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. // it.
TypeParameterListExpression expression = TypeParameterListExpression expression = setupTypeParameterListTemplate(builder, func);
setupTypeParameterListTemplate(builder, func, parameterTypeExpressions, returnTypeExpression, scope);
// the template built by TemplateBuilderImpl is ordered by element position, but we want types to be first, so hack it // 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(); final TemplateImpl template = (TemplateImpl) builder.buildInlineTemplate();
@@ -684,6 +760,39 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
}); });
} }
private Map<TypeParameterDescriptor, String> getTypeParameterRenames(JetScope scope) {
TypeParameterDescriptor[] receiverTypeParametersNotInScope = selectedReceiverType.getTypeParameters();
Set<TypeParameterDescriptor> allTypeParametersNotInScope = new LinkedHashSet<TypeParameterDescriptor>();
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<TypeParameterDescriptor> typeParameters = new ArrayList<TypeParameterDescriptor>(allTypeParametersNotInScope);
List<String> typeParameterNames = new ArrayList<String>();
for (TypeParameterDescriptor typeParameter : typeParameters) {
typeParameterNames.add(getNextAvailableName(typeParameter.getName().getName(), typeParameterNames, scope));
}
assert typeParameters.size() == typeParameterNames.size();
Map<TypeParameterDescriptor, String> typeParameterNameMap = new HashMap<TypeParameterDescriptor, String>();
for (int i = 0; i < typeParameters.size(); i++) {
typeParameterNameMap.put(typeParameters.get(i), typeParameterNames.get(i));
}
return typeParameterNameMap;
}
private void setupTypeReferencesForShortening( private void setupTypeReferencesForShortening(
@NotNull Project project, @NotNull Project project,
@NotNull JetNamedFunction func, @NotNull JetNamedFunction func,
@@ -692,8 +801,9 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
@Nullable TypeExpression returnTypeExpression @Nullable TypeExpression returnTypeExpression
) { ) {
if (isExtension) { if (isExtension) {
JetTypeReference receiverTypeRef = JetPsiFactory.createType(project, renderTypeLong(receiverType)); JetTypeReference receiverTypeRef =
replaceWithLongerName(project, receiverTypeRef, receiverType); JetPsiFactory.createType(project, renderTypeLong(selectedReceiverType.getType(), typeParameterNameMap));
replaceWithLongerName(project, receiverTypeRef, selectedReceiverType.getType());
receiverTypeRef = func.getReceiverTypeRef(); receiverTypeRef = func.getReceiverTypeRef();
assert receiverTypeRef != null; assert receiverTypeRef != null;
@@ -766,46 +876,33 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
private TypeExpression setupReturnTypeTemplate(@NotNull TemplateBuilder builder, @NotNull JetNamedFunction func) { private TypeExpression setupReturnTypeTemplate(@NotNull TemplateBuilder builder, @NotNull JetNamedFunction func) {
JetTypeReference returnTypeRef = func.getReturnTypeRef(); JetTypeReference returnTypeRef = func.getReturnTypeRef();
assert returnTypeRef != null; assert returnTypeRef != null;
TypeExpression returnTypeExpression = new TypeExpression(returnType.getPossibleTypes()); TypeExpression returnTypeExpression = new TypeExpression(returnType);
builder.replaceElement(returnTypeRef, returnTypeExpression); builder.replaceElement(returnTypeRef, returnTypeExpression);
return returnTypeExpression; return returnTypeExpression;
} }
@NotNull @NotNull
private TypeParameterListExpression setupTypeParameterListTemplate( private TypeParameterListExpression setupTypeParameterListTemplate(@NotNull TemplateBuilderImpl builder, @NotNull JetNamedFunction func) {
@NotNull TemplateBuilderImpl builder, Map<String, String[]> typeParameterMap = new HashMap<String, String[]>();
@NotNull JetNamedFunction func, String[] receiverTypeParameterNames = selectedReceiverType.getTypeParameterNames();
@NotNull TypeExpression[] parameterTypeExpressions,
@Nullable TypeExpression returnTypeExpression, for (Parameter parameter : parameters) {
@NotNull JetScope scope TypeCandidate[] parameterTypeCandidates = parameter.getType().getTypeCandidates();
) { for (TypeCandidate parameterTypeCandidate : parameterTypeCandidates) {
Map<String, TypeParameterDescriptor[]> typeParameterMap = new HashMap<String, TypeParameterDescriptor[]>(); typeParameterMap.put(parameterTypeCandidate.getRenderedType(), parameterTypeCandidate.getTypeParameterNames());
Set<TypeParameterDescriptor> 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<TypeParameterDescriptor> typeParameters = getTypeParametersInType(parameterTypeOptions[i]);
typeParameterMap.put(parameterTypeOptionStrings[i], getTypeParameterNamesNotInScope(typeParameters, scope));
} }
} }
JetTypeReference returnTypeRef = func.getReturnTypeRef(); JetTypeReference returnTypeRef = func.getReturnTypeRef();
if (returnTypeRef != null) { if (returnTypeRef != null) {
assert returnTypeExpression != null; TypeCandidate[] returnTypeCandidates = returnType.getTypeCandidates();
JetType[] returnTypeOptions = returnTypeExpression.getOptions(); for (TypeCandidate returnTypeCandidate : returnTypeCandidates) {
String[] returnTypeOptionStrings = returnTypeExpression.getOptionStrings(); typeParameterMap.put(returnTypeCandidate.getRenderedType(), returnTypeCandidate.getTypeParameterNames());
assert returnTypeOptions.length == returnTypeOptionStrings.length;
for (int i = 0; i < returnTypeOptions.length; i++) {
Set<TypeParameterDescriptor> typeParameters = getTypeParametersInType(returnTypeOptions[i]);
typeParameterMap.put(returnTypeOptionStrings[i], getTypeParameterNamesNotInScope(typeParameters, scope));
} }
} }
builder.replaceElement(func, TextRange.create(3, 3), TYPE_PARAMETER_LIST_VARIABLE_NAME, null, false); // ((3, 3) is after "fun") 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, private TypeExpression[] setupParameterTypeTemplates(@NotNull Project project, @NotNull TemplateBuilder builder,
@@ -813,13 +910,13 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
List<JetParameter> jetParameters = parameterList.getParameters(); List<JetParameter> jetParameters = parameterList.getParameters();
assert jetParameters.size() == parameters.size(); assert jetParameters.size() == parameters.size();
TypeExpression[] parameterTypeExpressions = new TypeExpression[parameters.size()]; TypeExpression[] parameterTypeExpressions = new TypeExpression[parameters.size()];
JetNameValidator dummyValidator = JetNameValidator.getEmptyValidator(project);
for (int i = 0; i < parameters.size(); i++) { for (int i = 0; i < parameters.size(); i++) {
Parameter parameter = parameters.get(i); Parameter parameter = parameters.get(i);
JetParameter jetParameter = jetParameters.get(i); JetParameter jetParameter = jetParameters.get(i);
// add parameter type to the template // add parameter type to the template
JetType[] typeOptions = parameter.getType().getPossibleTypes(); parameterTypeExpressions[i] = new TypeExpression(parameter.getType());
parameterTypeExpressions[i] = new TypeExpression(typeOptions);
JetTypeReference parameterTypeRef = jetParameter.getTypeReference(); JetTypeReference parameterTypeRef = jetParameter.getTypeReference();
assert parameterTypeRef != null; assert parameterTypeRef != null;
builder.replaceElement(parameterTypeRef, parameterTypeExpressions[i]); builder.replaceElement(parameterTypeRef, parameterTypeExpressions[i]);
@@ -838,11 +935,9 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
// figure out suggested names for each type option // figure out suggested names for each type option
Map<String, String[]> parameterTypeToNamesMap = new HashMap<String, String[]>(); Map<String, String[]> parameterTypeToNamesMap = new HashMap<String, String[]>();
String[] typeOptionStrings = parameterTypeExpressions[i].getOptionStrings(); for (TypeCandidate typeCandidate : parameter.getType().getTypeCandidates()) {
assert typeOptions.length == typeOptionStrings.length; String[] suggestedNames = JetNameSuggester.suggestNamesForType(typeCandidate.getType(), dummyValidator);
for (int j = 0; j < typeOptions.length; j++) { parameterTypeToNamesMap.put(typeCandidate.getRenderedType(), suggestedNames);
String[] suggestedNames = JetNameSuggester.suggestNamesForType(typeOptions[j], JetNameValidator.getEmptyValidator(project));
parameterTypeToNamesMap.put(typeOptionStrings[j], suggestedNames);
} }
// add expression to builder // add expression to builder
@@ -854,31 +949,8 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
return parameterTypeExpressions; return parameterTypeExpressions;
} }
@Override private void replaceWithLongerName(@NotNull Project project, @NotNull JetTypeReference typeRef, @NotNull JetType type) {
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { JetTypeReference fullyQualifiedReceiverTypeRef = JetPsiFactory.createType(project, renderTypeLong(type, typeParameterNameMap));
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));
typeRef.replace(fullyQualifiedReceiverTypeRef); typeRef.replace(fullyQualifiedReceiverTypeRef);
} }
@@ -933,13 +1005,45 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
} }
@NotNull @NotNull
private static String renderTypeShort(@NotNull JetType type) { private static String renderDescriptor(
return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(type); @NotNull DeclarationDescriptor declarationDescriptor,
@NotNull Map<TypeParameterDescriptor, String> 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<TypeParameterDescriptor, String> typeParameterNameMap, boolean fq) {
List<TypeProjection> projections = type.getArguments();
DeclarationDescriptor declarationDescriptor = type.getConstructor().getDeclarationDescriptor();
assert declarationDescriptor != null;
if (projections.isEmpty()) {
return renderDescriptor(declarationDescriptor, typeParameterNameMap, fq);
}
List<String> arguments = new ArrayList<String>();
for (TypeProjection projection : projections) {
arguments.add(renderTypeLong(projection.getType(), typeParameterNameMap));
}
return renderDescriptor(declarationDescriptor, typeParameterNameMap, fq) + "<" + StringUtil.join(arguments, ", ") + ">";
} }
@NotNull @NotNull
private static String renderTypeLong(@NotNull JetType type) { private static String renderTypeShort(@NotNull JetType type, @NotNull Map<TypeParameterDescriptor, String> typeParameterNameMap) {
return DescriptorRenderer.TEXT.renderType(type); return renderType(type, typeParameterNameMap, false);
}
@NotNull
private static String renderTypeLong(@NotNull JetType type, @NotNull Map<TypeParameterDescriptor, String> typeParameterNameMap) {
return renderType(type, typeParameterNameMap, true);
} }
@NotNull @NotNull
@@ -984,6 +1088,16 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
return name; return name;
} }
@NotNull
private static String getNextAvailableName(@NotNull String name, @NotNull Collection<String> 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 @NotNull
private static JetType[] guessTypeForExpression(@NotNull JetExpression expr, @NotNull BindingContext context) { private static JetType[] guessTypeForExpression(@NotNull JetExpression expr, @NotNull BindingContext context) {
JetType type = context.get(BindingContext.EXPRESSION_TYPE, expr); JetType type = context.get(BindingContext.EXPRESSION_TYPE, expr);
@@ -1070,12 +1184,6 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
return KotlinBuiltIns.getInstance().isUnit(type); return KotlinBuiltIns.getInstance().isUnit(type);
} }
private static void assertNoUnitTypes(@NotNull JetType[] types) {
for (JetType type : types) {
assert !isUnit(type);
}
}
@NotNull @NotNull
public static JetIntentionActionFactory createCreateGetMethodFromUsageFactory() { public static JetIntentionActionFactory createCreateGetMethodFromUsageFactory() {
return new JetIntentionActionFactory() { return new JetIntentionActionFactory() {