diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml
index 2454c6f99465..9c800fbdc34c 100644
--- a/java/java-impl/src/META-INF/JavaPlugin.xml
+++ b/java/java-impl/src/META-INF/JavaPlugin.xml
@@ -1151,6 +1151,7 @@
+
diff --git a/java/java-impl/src/com/intellij/codeInsight/template/macro/TypeParameterOfMacro.java b/java/java-impl/src/com/intellij/codeInsight/template/macro/TypeParameterOfMacro.java
new file mode 100644
index 000000000000..1f5e4dcbfff2
--- /dev/null
+++ b/java/java-impl/src/com/intellij/codeInsight/template/macro/TypeParameterOfMacro.java
@@ -0,0 +1,88 @@
+// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.codeInsight.template.macro;
+
+import com.intellij.codeInsight.template.*;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiTypesUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Bas Leijdekkers
+ */
+public class TypeParameterOfMacro extends Macro {
+ @Override
+ public String getName() {
+ return "typeParameterOf";
+ }
+
+ @Override
+ public String getPresentableName() {
+ return getName() + "(VAR[,indexOrName])";
+ }
+
+ @Override
+ public @NotNull String getDefaultValue() {
+ return "A";
+ }
+
+ @Override
+ public @Nullable Result calculateResult(Expression @NotNull [] params, ExpressionContext context) {
+ if (params.length == 0) return null;
+ final Result result = params[0].calculateResult(context);
+ if (result == null) {
+ return null;
+ }
+
+ final PsiType type;
+ if (result instanceof PsiTypeResult typeResult) {
+ type = typeResult.getType();
+ }
+ else {
+ final PsiExpression expression = MacroUtil.resultToPsiExpression(result, context);
+ if (expression == null) {
+ return null;
+ }
+ type = expression.getType();
+ }
+ if (type instanceof PsiClassType) {
+ final PsiClassType classType = (PsiClassType)type;
+ final PsiType[] parameters = classType.getParameters();
+ if (params.length > 1) {
+ final Result result1 = params[1].calculateResult(context);
+ if (result1 == null) {
+ return null;
+ }
+ final String value = result1.toString();
+ try {
+ final int i = Integer.parseInt(value);
+ return new PsiTypeResult(parameters[i], context.getProject());
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ catch (NumberFormatException e) {
+ final PsiClass aClass = PsiTypesUtil.getPsiClass(classType);
+ if (aClass == null) return null;
+ PsiTypeParameter @NotNull [] typeParameters = aClass.getTypeParameters();
+ for (int i = 0; i < typeParameters.length; i++) {
+ PsiTypeParameter parameter = typeParameters[i];
+ if (value.equals(parameter.getName())) {
+ return new PsiTypeResult(parameters[i], context.getProject());
+ }
+ }
+ return null;
+ }
+ }
+ else {
+ return new PsiTypeResult(parameters[0], context.getProject());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isAcceptableInContext(TemplateContextType context) {
+ return context instanceof JavaCodeContextType;
+ }
+}