From 4ffced1811dd6add9ce4ecdc844097b1fb568237 Mon Sep 17 00:00:00 2001 From: Marcus Mews Date: Mon, 16 Feb 2026 11:13:24 +0100 Subject: [PATCH] PY-79201 reveal inferred variance of type var GitOrigin-RevId: 300ca43057147f83290e2e232c4b8fa91d643876 --- .../documentation/PyDocumentationBuilder.java | 22 ++--- .../PythonDocumentationProvider.java | 86 +++++++++++++++++-- .../unusedTypeParameterInClass.py | 7 ++ .../quickdoc/TypeParameter_variance_co.html | 1 + .../quickdoc/TypeParameter_variance_co.py | 3 + .../TypeParameter_variance_contra.html | 1 + .../quickdoc/TypeParameter_variance_contra.py | 3 + .../quickdoc/TypeParameter_variance_inv.html | 1 + .../quickdoc/TypeParameter_variance_inv.py | 2 + ...TypeParameter_variance_not_affected_1.html | 1 + .../TypeParameter_variance_not_affected_1.py | 1 + ...TypeParameter_variance_not_affected_2.html | 1 + .../TypeParameter_variance_not_affected_2.py | 6 ++ ...Parameter_variance_on_its_declaration.html | 1 + ...peParameter_variance_on_its_declaration.py | 3 + .../TypeParameter_variance_with_default.html | 1 + .../TypeParameter_variance_with_default.py | 3 + .../quickdoc/TypeVar_variance_co.html | 1 + .../testData/quickdoc/TypeVar_variance_co.py | 7 ++ .../quickdoc/TypeVar_variance_contra.html | 1 + .../quickdoc/TypeVar_variance_contra.py | 7 ++ .../quickdoc/TypeVar_variance_in_init.html | 1 + .../quickdoc/TypeVar_variance_in_init.py | 9 ++ .../quickdoc/TypeVar_variance_inv.html | 1 + .../testData/quickdoc/TypeVar_variance_inv.py | 7 ++ .../TypeVar_variance_not_affected_1.html | 1 + .../TypeVar_variance_not_affected_1.py | 9 ++ .../TypeVar_variance_not_affected_2.html | 1 + .../TypeVar_variance_not_affected_2.py | 6 ++ .../com/jetbrains/python/Py3QuickDocTest.java | 67 ++++++++++++++- 30 files changed, 240 insertions(+), 21 deletions(-) create mode 100644 python/testData/inspections/PyUnusedLocalInspection/unusedTypeParameterInClass.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_co.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_co.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_contra.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_contra.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_inv.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_inv.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_not_affected_1.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_not_affected_1.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_not_affected_2.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_not_affected_2.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_on_its_declaration.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_on_its_declaration.py create mode 100644 python/testData/quickdoc/TypeParameter_variance_with_default.html create mode 100644 python/testData/quickdoc/TypeParameter_variance_with_default.py create mode 100644 python/testData/quickdoc/TypeVar_variance_co.html create mode 100644 python/testData/quickdoc/TypeVar_variance_co.py create mode 100644 python/testData/quickdoc/TypeVar_variance_contra.html create mode 100644 python/testData/quickdoc/TypeVar_variance_contra.py create mode 100644 python/testData/quickdoc/TypeVar_variance_in_init.html create mode 100644 python/testData/quickdoc/TypeVar_variance_in_init.py create mode 100644 python/testData/quickdoc/TypeVar_variance_inv.html create mode 100644 python/testData/quickdoc/TypeVar_variance_inv.py create mode 100644 python/testData/quickdoc/TypeVar_variance_not_affected_1.html create mode 100644 python/testData/quickdoc/TypeVar_variance_not_affected_1.py create mode 100644 python/testData/quickdoc/TypeVar_variance_not_affected_2.html create mode 100644 python/testData/quickdoc/TypeVar_variance_not_affected_2.py diff --git a/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 47975281d6e2..765b578441d3 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -185,7 +185,7 @@ public final class PyDocumentationBuilder { if (myBody.isEmpty() && myContent.isEmpty()) { - return null; // got nothing substantial to say! + return null; // got nothing significant to say! } else { final HtmlBuilder result = new HtmlBuilder(); @@ -217,7 +217,7 @@ public final class PyDocumentationBuilder { if (elementType == PyTokenTypes.FROM_KEYWORD) { // We want to show yield doc in 'yield from ...' expressions when hover to 'from', - // but there is no particular PyStatement for 'yield' keyword, therefore we make such a check. + // but there is no particular PyStatement for 'yield' keyword; therefore, we make such a check. if (parentStatement.getFirstChild() instanceof PyYieldExpression) { return PyNames.YIELD; } @@ -302,7 +302,7 @@ public final class PyDocumentationBuilder { } if (link != null && typeParamName != null) { myBody.appendRaw(PyPsiBundle.message("QDOC.type.parameter.name.of.link", HtmlChunk.text(typeParamName).bold(), link)).br(); - myBody.append(PythonDocumentationProvider.describeTypeParameter(typeParameter, true, myContext)); + myBody.append(PythonDocumentationProvider.describeTypeParameter(typeParameter, myOriginalElement, true, myContext)); } } @@ -476,7 +476,7 @@ public final class PyDocumentationBuilder { } if (effectiveDocstring != null) { addFunctionSpecificSections(effectiveDocstring, pyFunction); - // if function is init without doc we will take attributes from the class doc + // if the function is init without a doc, we will take attributes from the class doc if (effectiveDocstring != ownDocstring && PyUtil.isInitOrNewMethod(pyFunction)) { addAttributesSection(effectiveDocstring); } @@ -516,7 +516,7 @@ public final class PyDocumentationBuilder { if (owner instanceof PyClass pyClass) { final PyFunction init = pyClass.findMethodByName(PyNames.INIT, false, myContext); - // if class doesn't have any doc return init doc + // if the class doesn't have any doc return init doc if (init != null) { return getEffectiveDocStringExpression(init); } @@ -560,7 +560,7 @@ public final class PyDocumentationBuilder { } myBody.br(); } - // if there is no separate doc for attribute we will try to take it from class doc + // if there is no separate doc for attribute, we will try to take it from class doc if (getEffectiveDocStringExpression(target) == null) { final PyStringLiteralExpression docString = getEffectiveDocStringExpression(containingClass); if (docString != null) { @@ -572,7 +572,7 @@ public final class PyDocumentationBuilder { } } } - myBody.append(PythonDocumentationProvider.describeTarget(target, myContext)); + myBody.append(PythonDocumentationProvider.describeTarget(target, myOriginalElement, myContext)); } private void addAttributesSection(@NotNull PyStringLiteralExpression docstring) { @@ -635,7 +635,7 @@ public final class PyDocumentationBuilder { } } } - // Reference expression can be passed as the target element in Python console + // Reference expression can be passed as the target element in the Python console if (myElement instanceof PyReferenceExpression) { final PsiElement resolved = resolve((PyReferenceExpression)myElement); if (resolved != null) { @@ -669,7 +669,7 @@ public final class PyDocumentationBuilder { final boolean isConstructor = PyUtil.isInitOrNewMethod(pyFunction); List classes = pyClass.getAncestorClasses(myContext); if (isConstructor) { - // look at our own class again and maybe inherit class's doc + // look at our own class again and maybe inherit the class's doc classes = ContainerUtil.prepend(classes, pyClass); } for (PyClass ancestor : classes) { @@ -737,7 +737,7 @@ public final class PyDocumentationBuilder { .takeWhile(line -> !line.startsWith(">>>")) //TODO: PyConsoleUtil.ORDINARY_PROMPT .toList(); final HtmlBuilder result = new HtmlBuilder(); - // reconstruct back, dropping first empty fragment as needed + // reconstruct back, dropping the first empty fragment as needed boolean isFirstLine = true; final int tabSize = PythonCodeStyleService.getInstance().getTabSize(element.getContainingFile()); for (@NlsSafe String line : updatedLines) { @@ -821,7 +821,7 @@ public final class PyDocumentationBuilder { private static @Nullable HtmlChunk getLinkToFunction(@NotNull PyFunction function, boolean preferQualifiedName) { final String qualifiedName = function.getQualifiedName(); final PyClass pyClass = function.getContainingClass(); - // Preserve name of a containing class even if the whole qualified name can't be constructed + // Preserve the name of a containing class even if the whole qualified name can't be constructed final String shortName = pyClass == null ? function.getName() : pyClass.getName() + "." + function.getName(); final String linkText = preferQualifiedName && qualifiedName != null ? qualifiedName : shortName; diff --git a/python/python-psi-impl/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/python-psi-impl/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 80ca35844ac4..d6154b933622 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/python-psi-impl/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -6,6 +6,7 @@ import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.HtmlBuilder; import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.openapi.util.text.StringUtil; @@ -62,9 +63,12 @@ import com.jetbrains.python.psi.impl.PyClassImpl; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.types.PyCallableParameter; +import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyCollectionType; +import com.jetbrains.python.psi.types.PyInferredVarianceJudgment; import com.jetbrains.python.psi.types.PyTupleType; import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.PyTypeVarType; import com.jetbrains.python.psi.types.PyTypeVisitor; import com.jetbrains.python.psi.types.TypeEvalContext; import one.util.streamex.StreamEx; @@ -77,6 +81,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Locale; import static com.jetbrains.python.documentation.PyDocSignaturesHighlighterKt.functionNameTextAttribute; import static com.jetbrains.python.documentation.PyDocSignaturesHighlighterKt.highlightExpressionText; @@ -84,6 +89,7 @@ import static com.jetbrains.python.documentation.PyDocSignaturesHighlighterKt.pa import static com.jetbrains.python.documentation.PyDocSignaturesHighlighterKt.styledReference; import static com.jetbrains.python.documentation.PyDocSignaturesHighlighterKt.styledSpan; import static com.jetbrains.python.psi.PyUtil.as; +import static com.jetbrains.python.psi.types.PyInferredVarianceJudgment.isEffectivelyInvariant; /** * Provides quick docs for classes, methods, and functions. @@ -144,7 +150,7 @@ public class PythonDocumentationProvider implements DocumentationProvider { return describeExpression((PyExpression)element, referenceElement, context); } else if (element instanceof PyTypeParameter typeParameter) { - return PyPsiBundle.message("QDOC.type.parameter.name", describeTypeParameter(typeParameter, true, context)); + return PyPsiBundle.message("QDOC.type.parameter.name", describeTypeParameter(typeParameter, originalElement, true, context)); } else if (element instanceof PyTypeAliasStatement typeAliasStatement) { return describeTypeAlias(typeAliasStatement, context).toString(); @@ -158,11 +164,14 @@ public class PythonDocumentationProvider implements DocumentationProvider { return HtmlChunk.raw(describeFunctionWithTypes(function, context, forTooltip)); } - static @NotNull HtmlChunk describeTarget(@NotNull PyTargetExpression target, @NotNull TypeEvalContext context) { + static @NotNull HtmlChunk describeTarget(@NotNull PyTargetExpression target, + @NotNull PsiElement originalElement, + @NotNull TypeEvalContext context) { final HtmlBuilder result = new HtmlBuilder(); result.append(styledSpan(StringUtil.notNullize(target.getName()), DefaultLanguageHighlighterColors.IDENTIFIER)); result.append(styledSpan(": ", PyHighlighter.PY_OPERATION_SIGN)); - result.append(styledSpan(formatTypeWithLinks(context.getType(target), target, target, context), PyHighlighter.PY_ANNOTATION)); + PyType type = context.getType(target); + result.append(styledSpan(formatTypeWithLinks(type, target, target, context), PyHighlighter.PY_ANNOTATION)); // Can return not physical elements such as foo()[0] for assignments like x, _ = foo() final PyExpression value = target.findAssignedValue(); @@ -177,6 +186,27 @@ public class PythonDocumentationProvider implements DocumentationProvider { result.append(highlightExpressionText(initializerText.substring(0, index), value)); result.append(styledSpan("...", PyHighlighter.PY_DOT)); } + if (type instanceof PyClassType classType && PyTypingTypeProvider.TYPE_VAR.equals(classType.getClassQName())) { + Ref typeRef = PyTypingTypeProvider.getType(value, context); + if (typeRef != null && typeRef.get() instanceof PyTypeVarType valueType) { + PsiElement refExpr = findReferenceOrTypeParameter(originalElement); + if (refExpr != null) { + if (isEffectivelyInvariant(refExpr, context)) { + if (valueType.getVariance() != PyTypeVarType.Variance.INVARIANT) { + String varianceStr = PyTypeVarType.Variance.INVARIANT.name().toLowerCase(Locale.ROOT); //NON-NLS + result.append(styledSpan(" # treated as " + varianceStr, PyHighlighter.PY_LINE_COMMENT)); //NON-NLS + } + } + else if (valueType.getVariance() == PyTypeVarType.Variance.INFER_VARIANCE) { + PyTypeVarType.Variance inferredVariance = PyInferredVarianceJudgment.getInferredVariance(refExpr, context); + if (inferredVariance != null) { + String varianceStr = inferredVariance.name().toLowerCase(Locale.ROOT); //NON-NLS + result.append(styledSpan(" # inferred as " + varianceStr, PyHighlighter.PY_LINE_COMMENT)); //NON-NLS + } + } + } + } + } } return result.toFragment(); } @@ -190,14 +220,39 @@ public class PythonDocumentationProvider implements DocumentationProvider { } static @NotNull HtmlChunk describeTypeParameter(@NotNull PyTypeParameter typeParameter, + @Nullable PsiElement originalElement, boolean showKind, @NotNull TypeEvalContext context) { HtmlBuilder result = new HtmlBuilder(); + PsiElement refExpr = findReferenceOrTypeParameter(originalElement); + boolean effectivelyInvariant = isEffectivelyInvariant(refExpr, context); + PyTypeVarType.Variance variance = effectivelyInvariant + ? PyTypeVarType.Variance.INVARIANT + : PyInferredVarianceJudgment.getInferredVariance(refExpr, context); + String varianceStr = switch (variance) { + case null -> null; + case INFER_VARIANCE -> null; + case COVARIANT -> "out "; //NON-NLS + case CONTRAVARIANT -> "in "; //NON-NLS + case INVARIANT -> "invariant "; //NON-NLS + case BIVARIANT -> "bivariant "; //NON-NLS + }; + if (varianceStr != null) { + result.append(styledSpan(varianceStr, PyHighlighter.PY_KEYWORD)); + } result.append(styledSpan(StringUtil.notNullize(typeParameter.getName()), PyHighlighter.PY_TYPE_PARAMETER)); PyExpression boundExpression = typeParameter.getBoundExpression(); - if (boundExpression != null && typeParameter.getBoundExpressionText() != null) { + Ref boundExprTR = boundExpression == null ? null : PyTypingTypeProvider.getType(boundExpression, context); + if (boundExprTR != null && boundExprTR.get() != null) { result.append(styledSpan(": ", PyHighlighter.PY_OPERATION_SIGN)); - result.append(highlightExpressionText(typeParameter.getBoundExpressionText(), typeParameter.getBoundExpression())); + result.append(styledSpan(formatTypeWithLinks(boundExprTR.get(), boundExpression, boundExpression, context), + PyHighlighter.PY_ANNOTATION)); + } + PyExpression defaultExpr = typeParameter.getDefaultExpression(); + Ref defaultExprTR = defaultExpr == null ? null : PyTypingTypeProvider.getType(defaultExpr, context); + if (defaultExprTR != null && defaultExprTR.get() != null) { + result.append(styledSpan(" = ", PyHighlighter.PY_OPERATION_SIGN)); + result.append(styledSpan(formatTypeWithLinks(defaultExprTR.get(), defaultExpr, defaultExpr, context), PyHighlighter.PY_ANNOTATION)); } if (showKind) { result @@ -210,6 +265,19 @@ public class PythonDocumentationProvider implements DocumentationProvider { return result.toFragment(); } + private static @Nullable PsiElement findReferenceOrTypeParameter(@Nullable PsiElement originalElement) { + if (originalElement == null) return originalElement; + if (originalElement instanceof PyReferenceExpression refExpr) return refExpr; + if (originalElement.getParent() instanceof PyTypeParameter typeParam) return typeParam; + if (originalElement.getParent() instanceof PyReferenceExpression refExpr) return refExpr; + PsiElement prevElement = PsiTreeUtil.prevLeaf(originalElement, true); + if (prevElement == null) return originalElement; + if (prevElement.getParent() instanceof PyTypeParameter typeParam) return typeParam; + if (prevElement instanceof PyReferenceExpression refExpr) return refExpr; + if (prevElement.getParent() instanceof PyReferenceExpression refExpr) return refExpr; + return originalElement; + } + static @NotNull HtmlChunk describeTypeAlias(@NotNull PyTypeAliasStatement typeAliasStatement, @NotNull TypeEvalContext context) { HtmlBuilder result = new HtmlBuilder(); @@ -220,7 +288,7 @@ public class PythonDocumentationProvider implements DocumentationProvider { result.append(styledSpan("[", PyHighlighter.PY_BRACKETS)); result.append(StreamEx .of(typeParameters) - .map(typeParameter -> describeTypeParameter(typeParameter, false, context)) + .map(typeParameter -> describeTypeParameter(typeParameter, null, false, context)) .collect(HtmlChunk.toFragment(styledSpan(", ", PyHighlighter.PY_COMMA)))); result.append(styledSpan("]", PyHighlighter.PY_BRACKETS)); } @@ -306,7 +374,7 @@ public class PythonDocumentationProvider implements DocumentationProvider { } else { paramName = StringUtil.notNullize(paramName, PyNames.UNNAMED_ELEMENT); - // Don't show type for "self" unless it's explicitly annotated + // Don't show a type for "self" unless it's explicitly annotated showType = !isSelf || (named != null && new PyTypingTypeProvider().getParameterType(named, function, context) != null); } @@ -379,7 +447,7 @@ public class PythonDocumentationProvider implements DocumentationProvider { * Render a type in a human-readable format. *

* The format is our own compact representation of types, intended to be used in inspection warnings, - * documentation and other user-facing messages. It's not PEP-484 compatible. + * documentation, and other user-facing messages. It's not PEP-484 compatible. * In particular callables are rendered as {@code (p1: T1, p2: T2, ...) -> R}, not as {@code Callable[[T1, T2, ...], R]}. * To render a type as a valid expression for a type annotation use {@link #getTypeHint(PyType, TypeEvalContext)}. * @@ -462,7 +530,7 @@ public class PythonDocumentationProvider implements DocumentationProvider { @NotNull TypeEvalContext context, @NotNull PsiElement anchor, @NotNull HtmlBuilder body) { - // Variable annotated with "typing.TypeAlias" marker is deliberately treated as having "Any" type + // Variable annotated with 'typing.TypeAlias' marker is deliberately treated as having "Any" type if (typeOwner instanceof PyTargetExpression && type == null) { PyAssignmentStatement assignment = as(typeOwner.getParent(), PyAssignmentStatement.class); if (assignment != null && PyTypingTypeProvider.isExplicitTypeAlias(assignment, context)) { diff --git a/python/testData/inspections/PyUnusedLocalInspection/unusedTypeParameterInClass.py b/python/testData/inspections/PyUnusedLocalInspection/unusedTypeParameterInClass.py new file mode 100644 index 000000000000..d39063b1c693 --- /dev/null +++ b/python/testData/inspections/PyUnusedLocalInspection/unusedTypeParameterInClass.py @@ -0,0 +1,7 @@ +class A[T]: + def f(self): + pass + +class B[S, T]: + def f(self, s: S): + pass \ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_co.html b/python/testData/quickdoc/TypeParameter_variance_co.html new file mode 100644 index 000000000000..1f13f4c5dfa1 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_co.html @@ -0,0 +1 @@ +

Type parameter X of TypeParameter_variance_co.Box
out X, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_co.py b/python/testData/quickdoc/TypeParameter_variance_co.py new file mode 100644 index 000000000000..3a6edf6e64d1 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_co.py @@ -0,0 +1,3 @@ +class Box[X]: + def m(self) -> X: + pass \ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_contra.html b/python/testData/quickdoc/TypeParameter_variance_contra.html new file mode 100644 index 000000000000..0c16bb565881 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_contra.html @@ -0,0 +1 @@ +
Type parameter X of TypeParameter_variance_contra.Box
in X, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_contra.py b/python/testData/quickdoc/TypeParameter_variance_contra.py new file mode 100644 index 000000000000..d6fbc3c893ff --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_contra.py @@ -0,0 +1,3 @@ +class Box[X]: + def m(self, a: X): + pass \ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_inv.html b/python/testData/quickdoc/TypeParameter_variance_inv.html new file mode 100644 index 000000000000..3080222d0c77 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_inv.html @@ -0,0 +1 @@ +
Type parameter X of TypeParameter_variance_inv.Box
invariant X, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_inv.py b/python/testData/quickdoc/TypeParameter_variance_inv.py new file mode 100644 index 000000000000..1523f0e1e28a --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_inv.py @@ -0,0 +1,2 @@ +class Box[X]: + a: X diff --git a/python/testData/quickdoc/TypeParameter_variance_not_affected_1.html b/python/testData/quickdoc/TypeParameter_variance_not_affected_1.html new file mode 100644 index 000000000000..29d7517563bd --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_not_affected_1.html @@ -0,0 +1 @@ +
Type parameter YY of TypeParameter_variance_not_affected_1.m
invariant YY: int, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_not_affected_1.py b/python/testData/quickdoc/TypeParameter_variance_not_affected_1.py new file mode 100644 index 000000000000..246f990993cb --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_not_affected_1.py @@ -0,0 +1 @@ +def m[YY:int](self) -> YY: \ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_not_affected_2.html b/python/testData/quickdoc/TypeParameter_variance_not_affected_2.html new file mode 100644 index 000000000000..632b26b1691c --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_not_affected_2.html @@ -0,0 +1 @@ +
Type parameter X of TypeParameter_variance_not_affected_2.Box2
invariant X, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_not_affected_2.py b/python/testData/quickdoc/TypeParameter_variance_not_affected_2.py new file mode 100644 index 000000000000..d57cfd456ed3 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_not_affected_2.py @@ -0,0 +1,6 @@ +class Box2[X]: + def m(self) -> X: # inferred as 'out Any' + pass + @classmethod + def f(cls) -> X: # inferred as 'Any' + pass \ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_on_its_declaration.html b/python/testData/quickdoc/TypeParameter_variance_on_its_declaration.html new file mode 100644 index 000000000000..8383d43a386c --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_on_its_declaration.html @@ -0,0 +1 @@ +
Type parameter X of TypeParameter_variance_on_its_declaration.Box
out X, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_on_its_declaration.py b/python/testData/quickdoc/TypeParameter_variance_on_its_declaration.py new file mode 100644 index 000000000000..e5402ea78f7e --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_on_its_declaration.py @@ -0,0 +1,3 @@ +class Box[X]: + def m(self) -> X: + pass \ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_with_default.html b/python/testData/quickdoc/TypeParameter_variance_with_default.html new file mode 100644 index 000000000000..c26029917566 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_with_default.html @@ -0,0 +1 @@ +
Type parameter X of TypeParameter_variance_with_default.A
in X = int, kind: TypeVar
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeParameter_variance_with_default.py b/python/testData/quickdoc/TypeParameter_variance_with_default.py new file mode 100644 index 000000000000..8b6bf9406686 --- /dev/null +++ b/python/testData/quickdoc/TypeParameter_variance_with_default.py @@ -0,0 +1,3 @@ +class A[X = int]: + def f2(self, x: X): + pass \ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_co.html b/python/testData/quickdoc/TypeVar_variance_co.html new file mode 100644 index 000000000000..d3483d81f91b --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_co.html @@ -0,0 +1 @@ +
TT: TypeVar = TypeVar('TT', infer_variance=True) # inferred as covariant
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_co.py b/python/testData/quickdoc/TypeVar_variance_co.py new file mode 100644 index 000000000000..2110bebc9e93 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_co.py @@ -0,0 +1,7 @@ +from typing import TypeVar, Generic + +TT = TypeVar('TT', infer_variance=True) + +class C(Generic[TT]): + def f(self) -> TT: + pass diff --git a/python/testData/quickdoc/TypeVar_variance_contra.html b/python/testData/quickdoc/TypeVar_variance_contra.html new file mode 100644 index 000000000000..986402a2cef0 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_contra.html @@ -0,0 +1 @@ +
TT: TypeVar = TypeVar('TT', infer_variance=True) # inferred as contravariant
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_contra.py b/python/testData/quickdoc/TypeVar_variance_contra.py new file mode 100644 index 000000000000..67f69cc0fbe0 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_contra.py @@ -0,0 +1,7 @@ +from typing import TypeVar, Generic + +TT = TypeVar('TT', infer_variance=True) + +class C(Generic[TT]): + def f(self, p: TT): + pass diff --git a/python/testData/quickdoc/TypeVar_variance_in_init.html b/python/testData/quickdoc/TypeVar_variance_in_init.html new file mode 100644 index 000000000000..0aa925915ba2 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_in_init.html @@ -0,0 +1 @@ +
T: TypeVar = TypeVar('T', infer_variance=True) # treated as invariant
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_in_init.py b/python/testData/quickdoc/TypeVar_variance_in_init.py new file mode 100644 index 000000000000..a7b054988169 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_in_init.py @@ -0,0 +1,9 @@ +from typing import TypeVar, Generic + +T = TypeVar('T', infer_variance=True) + +class C(Generic[T]): + def __init__(self, t: T): + pass + def method(self) -> T: + pass diff --git a/python/testData/quickdoc/TypeVar_variance_inv.html b/python/testData/quickdoc/TypeVar_variance_inv.html new file mode 100644 index 000000000000..8d55efec2f50 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_inv.html @@ -0,0 +1 @@ +
TT: TypeVar = TypeVar('TT', infer_variance=True) # inferred as invariant
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_inv.py b/python/testData/quickdoc/TypeVar_variance_inv.py new file mode 100644 index 000000000000..8d24c71d7c37 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_inv.py @@ -0,0 +1,7 @@ +from typing import TypeVar, Generic + +TT = TypeVar('TT', infer_variance=True) + +class C(Generic[TT]): + def f(self, p: TT) -> TT: + pass diff --git a/python/testData/quickdoc/TypeVar_variance_not_affected_1.html b/python/testData/quickdoc/TypeVar_variance_not_affected_1.html new file mode 100644 index 000000000000..e62669b4032d --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_not_affected_1.html @@ -0,0 +1 @@ +
TT: TypeVar = TypeVar('TT', infer_variance=True) # inferred as invariant
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_not_affected_1.py b/python/testData/quickdoc/TypeVar_variance_not_affected_1.py new file mode 100644 index 000000000000..ff471a6d2454 --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_not_affected_1.py @@ -0,0 +1,9 @@ +from typing import TypeVar, Generic + +TT = TypeVar('TT', infer_variance=True) + +class C(Generic[TT]): + def method(self) -> TT: + pass + +def f() -> TT: \ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_not_affected_2.html b/python/testData/quickdoc/TypeVar_variance_not_affected_2.html new file mode 100644 index 000000000000..e67902c0f19e --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_not_affected_2.html @@ -0,0 +1 @@ +
TT: TypeVar = TypeVar('TT', covariant=True) # treated as invariant
\ No newline at end of file diff --git a/python/testData/quickdoc/TypeVar_variance_not_affected_2.py b/python/testData/quickdoc/TypeVar_variance_not_affected_2.py new file mode 100644 index 000000000000..537034d8298b --- /dev/null +++ b/python/testData/quickdoc/TypeVar_variance_not_affected_2.py @@ -0,0 +1,6 @@ +from typing import TypeVar, Generic + +TT = TypeVar('TT', covariant=True) + +class C(Generic[TT]): + def f() -> TT: ... \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/Py3QuickDocTest.java b/python/testSrc/com/jetbrains/python/Py3QuickDocTest.java index 54eae3936324..b49dac6c0f03 100644 --- a/python/testSrc/com/jetbrains/python/Py3QuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/Py3QuickDocTest.java @@ -843,6 +843,71 @@ public class Py3QuickDocTest extends LightMarkedTestCase { checkHTMLOnly(); } + // PY-87075 + public void testTypeParameter_variance_with_default() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeParameter_variance_inv() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeParameter_variance_co() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeParameter_variance_contra() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeParameter_variance_not_affected_1() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeParameter_variance_not_affected_2() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeParameter_variance_on_its_declaration() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeVar_variance_co() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeVar_variance_contra() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeVar_variance_inv() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeVar_variance_in_init() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeVar_variance_not_affected_1() { + checkHTMLOnly(); + } + + // PY-87075 + public void testTypeVar_variance_not_affected_2() { + checkHTMLOnly(); + } + // PY-64074 public void testTypeKeyword() { checkHTMLOnly(); @@ -852,7 +917,7 @@ public class Py3QuickDocTest extends LightMarkedTestCase { public void testTypeAliasStatement() { checkHTMLOnly(); } - + // PY-78119 public void testNeverType() { checkHTMLOnly();