PY-64074 Add Quick Documentation for type parameters and type alias statements

Merge-request: IJ-MR-119974
Merged-by: Daniil Kalinin <Daniil.Kalinin@jetbrains.com>

GitOrigin-RevId: fa0c57b3005b31d892a394b3a5f595ac10135a71
This commit is contained in:
Daniil Kalinin
2024-01-23 09:50:49 +00:00
committed by intellij-monorepo-bot
parent 583bea7f1a
commit c79da2708c
12 changed files with 217 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
<div class="section" id="the-type-statement">
<span id="type"></span><h2>The <a class="reference internal" href="#type"><tt class="xref std std-keyword docutils literal"><span class="pre">type</span></tt></a> statement</h2>
<pre id="index-47">
<strong id="grammar-token-python-grammar-type_stmt"><span id="grammar-token-type-stmt"></span>type_stmt</strong> ::= 'type' <a class="reference internal" href="lexical_analysis.html#grammar-token-python-grammar-identifier"><code class="xref docutils literal notranslate"><span class="pre">identifier</span></code></a> [<a class="reference internal" href="compound_stmts.html#grammar-token-python-grammar-type_params"><code class="xref docutils literal notranslate"><span class="pre">type_params</span></code></a>] &quot;=&quot; <a class="reference internal" href="expressions.html#grammar-token-python-grammar-expression"><code class="xref docutils literal notranslate"><span class="pre">expression</span></code></a>
</pre>
<p>The <code class="xref std std-keyword docutils literal notranslate"><span class="pre">type</span></code> statement declares a type alias, which is an instance
of <a class="reference internal" href="../library/typing.html#typing.TypeAliasType" title="typing.TypeAliasType"><code class="xref py py-class docutils literal notranslate"><span class="pre">typing.TypeAliasType</span></code></a>.</p>
<p>For example, the following statement creates a type alias:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="nb">type</span> <span class="n">Point</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span>
</pre></div>
</div>
<p>This code is roughly equivalent to:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">annotation</span><span class="o">-</span><span class="k">def</span> <span class="nf">VALUE_OF_Point</span><span class="p">():</span>
<span class="k">return</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span>
<span class="n">Point</span> <span class="o">=</span> <span class="n">typing</span><span class="o">.</span><span class="n">TypeAliasType</span><span class="p">(</span><span class="s2">&quot;Point&quot;</span><span class="p">,</span> <span class="n">VALUE_OF_Point</span><span class="p">())</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">annotation-def</span></code> indicates an <a class="reference internal" href="executionmodel.html#annotation-scopes"><span class="std std-ref">annotation scope</span></a>, which behaves
mostly like a function, but with several small differences.</p>
<p>The value of the
type alias is evaluated in the annotation scope. It is not evaluated when the
type alias is created, but only when the value is accessed through the type alias’s
<code class="xref py py-attr docutils literal notranslate"><span class="pre">__value__</span></code> attribute (see <a class="reference internal" href="executionmodel.html#lazy-evaluation"><span class="std std-ref">Lazy evaluation</span></a>).
This allows the type alias to refer to names that are not yet defined.</p>
<p>Type aliases may be made generic by adding a <a class="reference internal" href="compound_stmts.html#type-params"><span class="std std-ref">type parameter list</span></a>
after the name. See <a class="reference internal" href="compound_stmts.html#generic-type-aliases"><span class="std std-ref">Generic type aliases</span></a> for more.</p>
<p><code class="xref std std-keyword docutils literal notranslate"><span class="pre">type</span></code> is a <a class="reference internal" href="lexical_analysis.html#soft-keywords"><span class="std std-ref">soft keyword</span></a>.</p>
<div class="versionadded">
<p><span class="versionmodified added">New in version 3.12.</span></p>
</div>
<div class="admonition seealso">
<p class="admonition-title">See also</p>
<dl class="simple">
<dt><span class="target" id="index-48"></span><a class="pep reference external" href="https://peps.python.org/pep-0695/"><strong>PEP 695</strong></a> - Type Parameter Syntax</dt><dd><p>Introduced the <code class="xref std std-keyword docutils literal notranslate"><span class="pre">type</span></code> statement and syntax for
generic classes and functions.</p>
</dd>
</dl>
</div>
@@ -189,6 +189,10 @@ QDOC.variable.name=Variable "{0}"
QDOC.parameter.of.function.name=Parameter "{0}" of function "{1}"
QDOC.parameter.of.method.name=Parameter "{0}" of method "{1}"
QDOC.parameter.name=Parameter "{0}"
QDOC.type.parameter.name.of.link=Type parameter {0} of {1}
QDOC.type.parameter.name=Type parameter {0}
QDOC.type.parameter.kind=kind:
QDOC.type.alias.statement.name.of.link=Type alias statement {0} of {1}
### Formatter
formatter.panel.dict.alignment.do.not.align=Do not align
@@ -17,6 +17,8 @@ import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.jetbrains.python.*;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
@@ -85,6 +87,12 @@ public class PyDocumentationBuilder {
else if (elementDefinition instanceof PyNamedParameter) {
buildFromParameter((PyNamedParameter)elementDefinition);
}
else if (elementDefinition instanceof PyTypeParameter typeParameter) {
buildFromTypeParameter(typeParameter);
}
else if (elementDefinition instanceof PyTypeAliasStatement typeAliasStatement) {
buildFromTypeAliasStatement(typeAliasStatement);
}
final ASTNode node = elementDefinition.getNode();
if (node != null) {
@@ -230,6 +238,44 @@ public class PyDocumentationBuilder {
myBody.append(PythonDocumentationProvider.describeParameter(parameter, myContext));
}
private void buildFromTypeParameter(@NotNull PyTypeParameter typeParameter) {
ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(typeParameter);
HtmlChunk link = null;
String typeParamName = typeParameter.getName();
if (scopeOwner instanceof PyFunction pyFunction) {
link = getLinkToFunction(pyFunction, true);
}
else if (scopeOwner instanceof PyClass pyClass) {
link = getLinkToClass(pyClass, true);
}
else if (scopeOwner instanceof PyTypeAliasStatement typeAliasStatement) {
link = getLinkToTypeAliasStatement(typeAliasStatement);
}
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));
}
}
private void buildFromTypeAliasStatement(@NotNull PyTypeAliasStatement typeAliasStatement) {
ScopeOwner scopeOwner = ScopeUtil.getScopeOwner(typeAliasStatement);
HtmlChunk link = null;
String typeParamName = typeAliasStatement.getName();
if (scopeOwner instanceof PyFunction pyFunction) {
link = getLinkToFunction(pyFunction, true);
}
else if (scopeOwner instanceof PyClass pyClass) {
link = getLinkToClass(pyClass, true);
}
else if (scopeOwner instanceof PyFile pyFile) {
link = getLinkToModule(pyFile);
}
if (link != null && typeParamName != null) {
myBody.appendRaw(PyPsiBundle.message("QDOC.type.alias.statement.name.of.link", HtmlChunk.text(typeParamName).bold(), link)).br();
myBody.append(PythonDocumentationProvider.describeTypeAlias(typeAliasStatement, myContext));
}
}
private @NotNull HtmlChunk runFormatterService(@NotNull @Nls String description) {
final DocstringFormatterRequest output =
PyStructuredDocstringFormatter.formatDocstring(myElement, new DocstringFormatterRequest(description, Collections.emptyList()),
@@ -742,6 +788,16 @@ public class PyDocumentationBuilder {
return HtmlChunk.raw(linkText);
}
@Nullable
private static HtmlChunk getLinkToTypeAliasStatement(@NotNull PyTypeAliasStatement typeAliasStatement) {
final String linkText = typeAliasStatement.getQualifiedName();
final PsiFile file = typeAliasStatement.getContainingFile();
if (linkText == null || typeAliasStatement.getName() == null || file == null) {
return null;
}
return PyDocumentationLink.toTypeAliasStatement(linkText, typeAliasStatement);
}
@Nullable
static PyStringLiteralExpression getEffectiveDocStringExpression(@NotNull PyDocStringOwner owner) {
final PyStringLiteralExpression expression = owner.getDocStringExpression();
@@ -74,6 +74,15 @@ object PyDocumentationLink {
}
}
@JvmStatic
fun toTypeAliasStatement(@NlsSafe content: String, typeAliasStatement: PyTypeAliasStatement) : HtmlChunk {
val qualifiedName = typeAliasStatement.qualifiedName
return when {
qualifiedName != null -> HtmlChunk.link("${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$LINK_TYPE_FUNC$qualifiedName", content)
else -> HtmlChunk.text(content)
}
}
@JvmStatic
fun toModule(@NlsSafe content: String, qualifiedName: String): HtmlChunk {
return HtmlChunk.link("${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$LINK_TYPE_MODULE$qualifiedName", content)
@@ -92,6 +92,12 @@ public class PythonDocumentationProvider implements DocumentationProvider {
else if (element instanceof PyExpression) {
return describeExpression((PyExpression)element, referenceElement, context);
}
else if (element instanceof PyTypeParameter typeParameter) {
return PyPsiBundle.message("QDOC.type.parameter.name", describeTypeParameter(typeParameter, true, context));
}
else if (element instanceof PyTypeAliasStatement typeAliasStatement) {
return describeTypeAlias(typeAliasStatement, context).toString();
}
return null;
}
@@ -135,6 +141,49 @@ public class PythonDocumentationProvider implements DocumentationProvider {
return result.toFragment();
}
@NotNull
static HtmlChunk describeTypeParameter(@NotNull PyTypeParameter typeParameter, boolean showKind, @NotNull TypeEvalContext context) {
HtmlBuilder result = new HtmlBuilder();
result.append(styledSpan(StringUtil.notNullize(typeParameter.getName()), PyHighlighter.PY_TYPE_PARAMETER));
PyExpression boundExpression = typeParameter.getBoundExpression();
if (boundExpression != null && typeParameter.getBoundExpressionText() != null) {
result.append(styledSpan(": ", PyHighlighter.PY_OPERATION_SIGN));
result.append(highlightExpressionText(typeParameter.getBoundExpressionText(), typeParameter.getBoundExpression()));
}
if (showKind) {
result
.append(", ")
.append(PyPsiBundle.message("QDOC.type.parameter.kind"))
.append(" ")
.append(styledSpan(formatTypeWithLinks(context.getType(typeParameter), typeParameter, typeParameter, context), PyHighlighter.PY_ANNOTATION));
}
return result.toFragment();
}
@NotNull
static HtmlChunk describeTypeAlias(@NotNull PyTypeAliasStatement typeAliasStatement, @NotNull TypeEvalContext context) {
HtmlBuilder result = new HtmlBuilder();
result.append(styledSpan("type ", PyHighlighter.PY_KEYWORD)); //NON-NLS
result.append(styledSpan(StringUtil.notNullize(typeAliasStatement.getName()), DefaultLanguageHighlighterColors.IDENTIFIER));
if (typeAliasStatement.getTypeParameterList() != null) {
List<PyTypeParameter> typeParameters = typeAliasStatement.getTypeParameterList().getTypeParameters();
result.append(styledSpan("[", PyHighlighter.PY_BRACKETS));
result.append(StreamEx
.of(typeParameters)
.map(typeParameter -> describeTypeParameter(typeParameter, false, context))
.collect(HtmlChunk.toFragment(styledSpan(", ", PyHighlighter.PY_COMMA))));
result.append(styledSpan("]", PyHighlighter.PY_BRACKETS));
}
PyExpression typeExpression = typeAliasStatement.getTypeExpression();
if (typeExpression != null) {
result.append(styledSpan(" = ", PyHighlighter.PY_OPERATION_SIGN));
result.append(styledSpan(formatTypeWithLinks(context.getType(typeExpression),
typeExpression, typeAliasStatement, context), PyHighlighter.PY_ANNOTATION));
}
return result.toFragment();
}
@NlsSafe
@NotNull
private static String describeFunctionWithTypes(@NotNull PyFunction function,
@@ -0,0 +1 @@
<html><body><div class="definition"><pre>Type alias statement <b>my_dict</b> of <a href="psi_element://#module#TypeAliasStatement">TypeAliasStatement</a><br/><span style="color:#000080;font-weight:bold;">type </span><span style="color:#000000;">my_dict</span><span style="">[</span><span style="color:#000000;">T</span><span style="">, </span><span style="color:#000000;">U</span><span style="">]</span><span style=""> = </span><span style="color:#000000;">Type<span style="">[</span><span style="color:#000080;"><a href="psi_element://#typename#dict">dict</a></span><span style="">[</span>T<span style="">, </span>U<span style="">]</span><span style="">]</span></span></pre></div></body></html>
@@ -0,0 +1,3 @@
type my_dict[T, U] = dict[T, U]
x: my_d<the_ref>ict
+38
View File
@@ -0,0 +1,38 @@
<html><body><div class="content"><div class="section" id="the-type-statement">
<span id="type"></span><h2>The <a class="reference internal" href="#type"><tt class="xref std std-keyword docutils literal"><span class="pre">type</span></tt></a> statement</h2>
<pre id="index-47">
<strong id="grammar-token-python-grammar-type_stmt"><span id="grammar-token-type-stmt"></span>type_stmt</strong> ::= 'type' <a class="reference internal" href="lexical_analysis.html#grammar-token-python-grammar-identifier"><code class="xref docutils literal notranslate"><span class="pre">identifier</span></code></a> [<a class="reference internal" href="compound_stmts.html#grammar-token-python-grammar-type_params"><code class="xref docutils literal notranslate"><span class="pre">type_params</span></code></a>] &quot;=&quot; <a class="reference internal" href="expressions.html#grammar-token-python-grammar-expression"><code class="xref docutils literal notranslate"><span class="pre">expression</span></code></a>
</pre>
<p>The <code class="xref std std-keyword docutils literal notranslate"><span class="pre">type</span></code> statement declares a type alias, which is an instance
of <a class="reference internal" href="../library/typing.html#typing.TypeAliasType" title="typing.TypeAliasType"><code class="xref py py-class docutils literal notranslate"><span class="pre">typing.TypeAliasType</span></code></a>.</p>
<p>For example, the following statement creates a type alias:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="nb">type</span> <span class="n">Point</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span>
</pre></div>
</div>
<p>This code is roughly equivalent to:</p>
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="n">annotation</span><span class="o">-</span><span class="k">def</span> <span class="nf">VALUE_OF_Point</span><span class="p">():</span>
<span class="k">return</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]</span>
<span class="n">Point</span> <span class="o">=</span> <span class="n">typing</span><span class="o">.</span><span class="n">TypeAliasType</span><span class="p">(</span><span class="s2">&quot;Point&quot;</span><span class="p">,</span> <span class="n">VALUE_OF_Point</span><span class="p">())</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">annotation-def</span></code> indicates an <a class="reference internal" href="executionmodel.html#annotation-scopes"><span class="std std-ref">annotation scope</span></a>, which behaves
mostly like a function, but with several small differences.</p>
<p>The value of the
type alias is evaluated in the annotation scope. It is not evaluated when the
type alias is created, but only when the value is accessed through the type alias’s
<code class="xref py py-attr docutils literal notranslate"><span class="pre">__value__</span></code> attribute (see <a class="reference internal" href="executionmodel.html#lazy-evaluation"><span class="std std-ref">Lazy evaluation</span></a>).
This allows the type alias to refer to names that are not yet defined.</p>
<p>Type aliases may be made generic by adding a <a class="reference internal" href="compound_stmts.html#type-params"><span class="std std-ref">type parameter list</span></a>
after the name. See <a class="reference internal" href="compound_stmts.html#generic-type-aliases"><span class="std std-ref">Generic type aliases</span></a> for more.</p>
<p><code class="xref std std-keyword docutils literal notranslate"><span class="pre">type</span></code> is a <a class="reference internal" href="lexical_analysis.html#soft-keywords"><span class="std std-ref">soft keyword</span></a>.</p>
<div class="versionadded">
<p><span class="versionmodified added">New in version 3.12.</span></p>
</div>
<div class="admonition seealso">
<p class="admonition-title">See also</p>
<dl class="simple">
<dt><span class="target" id="index-48"></span><a class="pep reference external" href="https://peps.python.org/pep-0695/"><strong>PEP 695</strong></a> - Type Parameter Syntax</dt><dd><p>Introduced the <code class="xref std std-keyword docutils literal notranslate"><span class="pre">type</span></code> statement and syntax for
generic classes and functions.</p>
</dd>
</dl>
</div></div></body></html>
+1
View File
@@ -0,0 +1 @@
ty<the_ref>pe my_type[T: str] = list[T]
@@ -0,0 +1 @@
<html><body><div class="definition"><pre>Type parameter <b>T</b> of <a href="psi_element://#func#TypeParameter.foo">TypeParameter.foo</a><br/><span style="color:#000000;">T</span><span style="">: </span><span style="">(str,&#32;bytes)</span>, kind: <span style="color:#000000;">TypeVar</span></pre></div></body></html>
@@ -0,0 +1,2 @@
def foo[T: (str, bytes)](x: T<the_ref>) -> T:
return x
@@ -856,6 +856,21 @@ public class Py3QuickDocTest extends LightMarkedTestCase {
checkHTMLOnly();
}
// PY-64074
public void testTypeParameter() {
checkHTMLOnly();
}
// PY-64074
public void testTypeKeyword() {
checkHTMLOnly();
}
// PY-64074
public void testTypeAliasStatement() {
checkHTMLOnly();
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/quickdoc/";