mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-CR-63687: PY-41305 Add PEP 614 support
Allows to use any expression as a decorator GitOrigin-RevId: e92af1ebd2c4d7024971fd4542bfe52640faaa26
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d41cfccb7a
commit
2e617b803c
@@ -54,4 +54,8 @@ public interface PyDecorator extends PyCallExpression, StubBasedPsiElement<PyDec
|
||||
@Nullable
|
||||
QualifiedName getQualifiedName();
|
||||
|
||||
/**
|
||||
* True if its callee name consists only of identifiers and dots, e.g. {@code x.y.z}
|
||||
*/
|
||||
boolean hasPlainReferenceCallee();
|
||||
}
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ public class PyArgumentEqualDefaultInspection extends PyInspection {
|
||||
|
||||
@Override
|
||||
public void visitPyCallExpression(final PyCallExpression node) {
|
||||
if (node.getArgumentList() == null) {
|
||||
if (node.getParent() instanceof PyDecorator) {
|
||||
return;
|
||||
}
|
||||
final List<PyCallable> callables = node.multiResolveCalleeFunction(getResolveContext());
|
||||
|
||||
+2
@@ -66,6 +66,8 @@ public class PyCallingNonCallableInspection extends PyInspection {
|
||||
}
|
||||
|
||||
private void checkCallable(@NotNull PyElement node, @Nullable PyExpression callee) {
|
||||
if (node.getParent() instanceof PyDecorator) return; //we've already been here
|
||||
|
||||
if (callee != null && isCallable(callee, myTypeEvalContext) == Boolean.FALSE) {
|
||||
final PyType calleeType = myTypeEvalContext.getType(callee);
|
||||
String message = "Expression is not callable";
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.jetbrains.python.parsing;
|
||||
|
||||
import com.intellij.lang.SyntaxTreeBuilder;
|
||||
import com.intellij.lang.WhitespacesBinders;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
@@ -83,14 +82,9 @@ public class FunctionParsing extends Parsing {
|
||||
while (myBuilder.getTokenType() == PyTokenTypes.AT) {
|
||||
SyntaxTreeBuilder.Marker decoratorMarker = myBuilder.mark();
|
||||
myBuilder.advanceLexer();
|
||||
getStatementParser().parseDottedName();
|
||||
if (myBuilder.getTokenType() == PyTokenTypes.LPAR) {
|
||||
getExpressionParser().parseArgumentList();
|
||||
}
|
||||
else { // empty arglist node, so we always have it
|
||||
SyntaxTreeBuilder.Marker argListMarker = myBuilder.mark();
|
||||
argListMarker.setCustomEdgeTokenBinders(WhitespacesBinders.GREEDY_LEFT_BINDER, null);
|
||||
argListMarker.done(PyElementTypes.ARGUMENT_LIST);
|
||||
|
||||
if (!getExpressionParser().parseSingleExpression(false)) {
|
||||
myBuilder.error(message("PARSE.expected.expression"));
|
||||
}
|
||||
if (atToken(PyTokenTypes.STATEMENT_BREAK)) {
|
||||
decoratorMarker.done(PyElementTypes.DECORATOR_CALL);
|
||||
|
||||
@@ -68,8 +68,13 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
|
||||
|
||||
@Override
|
||||
public boolean hasArgumentList() {
|
||||
final ASTNode arglistNode = getNode().findChildByType(PyElementTypes.ARGUMENT_LIST);
|
||||
return (arglistNode != null) && (arglistNode.findChildByType(PyTokenTypes.LPAR) != null);
|
||||
return findChildByClass(PyCallExpression.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PyArgumentList getArgumentList() {
|
||||
final PyCallExpression callExpr = findChildByClass(PyCallExpression.class);
|
||||
return callExpr != null ? callExpr.getArgumentList() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +85,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
|
||||
return stub.getQualifiedName();
|
||||
}
|
||||
else {
|
||||
final PyReferenceExpression node = PsiTreeUtil.getChildOfType(this, PyReferenceExpression.class);
|
||||
final PyCallExpression callExpr = findChildByClass(PyCallExpression.class);
|
||||
final PyReferenceExpression node = PsiTreeUtil.getChildOfType(callExpr != null ? callExpr : this, PyReferenceExpression.class);
|
||||
if (node != null) {
|
||||
return node.asQualifiedName();
|
||||
}
|
||||
@@ -91,15 +97,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
|
||||
@Override
|
||||
@Nullable
|
||||
public PyExpression getCallee() {
|
||||
try {
|
||||
return (PyExpression)getFirstChild().getNextSibling(); // skip the @ before call
|
||||
}
|
||||
catch (NullPointerException npe) { // no sibling
|
||||
return null;
|
||||
}
|
||||
catch (ClassCastException cce) { // error node instead
|
||||
return null;
|
||||
}
|
||||
final PyExpression exprAfterAt = findChildByClass(PyExpression.class);
|
||||
return exprAfterAt instanceof PyCallExpression ? ((PyCallExpression)exprAfterAt).getCallee() : exprAfterAt;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -151,4 +150,10 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
|
||||
public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
|
||||
return PyCallExpressionHelper.getCallType(this, context, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPlainReferenceCallee() {
|
||||
final PyExpression callee = getCallee();
|
||||
return callee instanceof PyReferenceExpression && ((PyReferenceExpression)callee).asQualifiedName() != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiErrorElement;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
@@ -740,4 +741,16 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
|
||||
node);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyDecoratorList(PyDecoratorList node) {
|
||||
super.visitPyDecoratorList(node);
|
||||
|
||||
for (PyDecorator decorator : node.getDecorators()) {
|
||||
if (PsiTreeUtil.getChildOfType(decorator, PsiErrorElement.class) == null && !decorator.hasPlainReferenceCallee()) {
|
||||
registerForAllMatchingVersions(level -> level.isOlderThan(LanguageLevel.PYTHON39) && registerForLanguageLevel(level),
|
||||
" not support arbitrary expressions as a decorator", decorator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ public class HighlightingAnnotator extends PyAnnotator {
|
||||
|
||||
@Override
|
||||
public void visitPyCallExpression(PyCallExpression node) {
|
||||
if (node.getParent() instanceof PyDecorator) return; //if it's in decorator, then we've already highlighted it as a decorator
|
||||
final PyReferenceExpression callee = as(node.getCallee(), PyReferenceExpression.class);
|
||||
if (callee != null) {
|
||||
if (!callee.isQualified() && PyBuiltinCache.isInBuiltins(callee)) {
|
||||
|
||||
@@ -21,9 +21,10 @@ import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.highlighting.PyHighlighter;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Highlights class definitions, functrion definitions, and decorators.
|
||||
* User: dcheryasov
|
||||
@@ -79,9 +80,8 @@ public class PyDefinitionsAnnotator extends PyAnnotator {
|
||||
final PsiElement atSign = node.getFirstChild();
|
||||
if (atSign != null) {
|
||||
addHighlightingAnnotation(atSign, PyHighlighter.PY_DECORATOR);
|
||||
final PsiElement refExpression = PyPsiUtils.getNextNonWhitespaceSibling(atSign);
|
||||
if (refExpression != null) {
|
||||
addHighlightingAnnotation(refExpression, PyHighlighter.PY_DECORATOR);
|
||||
if (node.hasPlainReferenceCallee()) {
|
||||
addHighlightingAnnotation(Objects.requireNonNull(node.getCallee()), PyHighlighter.PY_DECORATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<info descr="PY.DECORATOR">@</info><info descr="PY.DECORATOR">foo.bar</info>
|
||||
def <info descr="PY.FUNC_DEFINITION">f</info>():
|
||||
pass
|
||||
|
||||
<info descr="PY.DECORATOR">@</info><info descr="PY.DECORATOR">foo</info>(1, 2, 3)
|
||||
def <info descr="PY.FUNC_DEFINITION">f</info>():
|
||||
pass
|
||||
|
||||
|
||||
<info descr="PY.DECORATOR">@</info>foo[1].bar
|
||||
def <info descr="PY.FUNC_DEFINITION">f</info>():
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
<warning descr="Python version 2.6, 2.7, 3.4, 3.5, 3.6, 3.7, 3.8 do not support arbitrary expressions as a decorator">@x[0][1]</warning>
|
||||
@my_decorator
|
||||
def say_whee():
|
||||
print("Whee!")
|
||||
|
||||
<warning descr="Python version 2.6, 2.7, 3.4, 3.5, 3.6, 3.7, 3.8 do not support arbitrary expressions as a decorator">@foo[0].wrapper</warning>
|
||||
@foo.bar()
|
||||
def say_whee():
|
||||
print("Whee!")
|
||||
@@ -40,6 +40,6 @@ def unused_inner_function_with_unknown_decorator():
|
||||
pass
|
||||
|
||||
def unused_inner_function_with_incomplete_decorator():
|
||||
@<EOLError descr="Identifier expected"></EOLError>
|
||||
@<EOLError descr="expression expected"></EOLError>
|
||||
def func(): # pass
|
||||
pass
|
||||
|
||||
@@ -17,8 +17,6 @@ PyFile:BadDecoratorNotMethod.py
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: staticmethod
|
||||
PsiElement(Py:IDENTIFIER)('staticmethod')
|
||||
PyArgumentList
|
||||
<empty list>
|
||||
PsiErrorElement:'@' or 'def' expected
|
||||
<empty list>
|
||||
PsiWhiteSpace('\n ')
|
||||
|
||||
@@ -5,15 +5,11 @@ PyFile:ClassDecorators.py
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: foo
|
||||
PsiElement(Py:IDENTIFIER)('foo')
|
||||
PyArgumentList
|
||||
<empty list>
|
||||
PsiWhiteSpace('\n')
|
||||
PyDecorator: @bar
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: bar
|
||||
PsiElement(Py:IDENTIFIER)('bar')
|
||||
PyArgumentList
|
||||
<empty list>
|
||||
PsiWhiteSpace('\n')
|
||||
PsiElement(Py:CLASS_KEYWORD)('class')
|
||||
PsiWhiteSpace(' ')
|
||||
|
||||
@@ -5,8 +5,6 @@ PyFile:CommentAfterDecorator.py
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: uncallable_deco
|
||||
PsiElement(Py:IDENTIFIER)('uncallable_deco')
|
||||
PyArgumentList
|
||||
<empty list>
|
||||
PsiWhiteSpace(' ')
|
||||
PsiComment(Py:END_OF_LINE_COMMENT)('# some comment')
|
||||
PsiWhiteSpace('\n')
|
||||
|
||||
@@ -5,8 +5,6 @@ PyFile:DecoratedAsyncDef.py
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: foo
|
||||
PsiElement(Py:IDENTIFIER)('foo')
|
||||
PyArgumentList
|
||||
<empty list>
|
||||
PsiWhiteSpace('\n')
|
||||
PsiElement(Py:ASYNC_KEYWORD)('async')
|
||||
PsiWhiteSpace(' ')
|
||||
@@ -36,17 +34,18 @@ PyFile:DecoratedAsyncDef.py
|
||||
PyDecoratorList
|
||||
PyDecorator: @baz
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: baz
|
||||
PsiElement(Py:IDENTIFIER)('baz')
|
||||
PyArgumentList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PyReferenceExpression: x
|
||||
PsiElement(Py:IDENTIFIER)('x')
|
||||
PsiElement(Py:COMMA)(',')
|
||||
PsiWhiteSpace(' ')
|
||||
PyReferenceExpression: y
|
||||
PsiElement(Py:IDENTIFIER)('y')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PyCallExpression: baz
|
||||
PyReferenceExpression: baz
|
||||
PsiElement(Py:IDENTIFIER)('baz')
|
||||
PyArgumentList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PyReferenceExpression: x
|
||||
PsiElement(Py:IDENTIFIER)('x')
|
||||
PsiElement(Py:COMMA)(',')
|
||||
PsiWhiteSpace(' ')
|
||||
PyReferenceExpression: y
|
||||
PsiElement(Py:IDENTIFIER)('y')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PsiWhiteSpace('\n')
|
||||
PsiElement(Py:ASYNC_KEYWORD)('async')
|
||||
PsiWhiteSpace(' ')
|
||||
|
||||
@@ -5,21 +5,20 @@ PyFile:DecoratedFunction.py
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: staticmethod
|
||||
PsiElement(Py:IDENTIFIER)('staticmethod')
|
||||
PyArgumentList
|
||||
<empty list>
|
||||
PsiWhiteSpace('\n')
|
||||
PyDecorator: @xmlize
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: xmlize
|
||||
PsiElement(Py:IDENTIFIER)('xmlize')
|
||||
PyArgumentList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PyKeywordArgumentImpl: node
|
||||
PsiElement(Py:IDENTIFIER)('node')
|
||||
PsiElement(Py:EQ)('=')
|
||||
PyStringLiteralExpression: foo
|
||||
PsiElement(Py:SINGLE_QUOTED_STRING)('"foo"')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PyCallExpression: xmlize
|
||||
PyReferenceExpression: xmlize
|
||||
PsiElement(Py:IDENTIFIER)('xmlize')
|
||||
PyArgumentList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PyKeywordArgumentImpl: node
|
||||
PsiElement(Py:IDENTIFIER)('node')
|
||||
PsiElement(Py:EQ)('=')
|
||||
PyStringLiteralExpression: foo
|
||||
PsiElement(Py:SINGLE_QUOTED_STRING)('"foo"')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PsiWhiteSpace('\n')
|
||||
PsiElement(Py:DEF_KEYWORD)('def')
|
||||
PsiWhiteSpace(' ')
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
@x[0]
|
||||
@y:=x[0]
|
||||
@my_decorator
|
||||
def say_whee():
|
||||
print("Whee!")
|
||||
|
||||
|
||||
@y,x
|
||||
def say_whee2():
|
||||
print("Whee!")
|
||||
@@ -0,0 +1,89 @@
|
||||
PyFile:ExpressionsInDecorators.py
|
||||
PyFunction('say_whee')
|
||||
PyDecoratorList
|
||||
PyDecorator: @x[0]
|
||||
PsiElement(Py:AT)('@')
|
||||
PySubscriptionExpression
|
||||
PyReferenceExpression: x
|
||||
PsiElement(Py:IDENTIFIER)('x')
|
||||
PsiElement(Py:LBRACKET)('[')
|
||||
PyNumericLiteralExpression
|
||||
PsiElement(Py:INTEGER_LITERAL)('0')
|
||||
PsiElement(Py:RBRACKET)(']')
|
||||
PsiWhiteSpace('\n')
|
||||
PyDecorator: @y:=x[0]
|
||||
PsiElement(Py:AT)('@')
|
||||
PyAssignmentExpression
|
||||
PyTargetExpression: y
|
||||
PsiElement(Py:IDENTIFIER)('y')
|
||||
PsiElement(Py:COLONEQ)(':=')
|
||||
PySubscriptionExpression
|
||||
PyReferenceExpression: x
|
||||
PsiElement(Py:IDENTIFIER)('x')
|
||||
PsiElement(Py:LBRACKET)('[')
|
||||
PyNumericLiteralExpression
|
||||
PsiElement(Py:INTEGER_LITERAL)('0')
|
||||
PsiElement(Py:RBRACKET)(']')
|
||||
PsiWhiteSpace('\n')
|
||||
PyDecorator: @my_decorator
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: my_decorator
|
||||
PsiElement(Py:IDENTIFIER)('my_decorator')
|
||||
PsiWhiteSpace('\n')
|
||||
PsiElement(Py:DEF_KEYWORD)('def')
|
||||
PsiWhiteSpace(' ')
|
||||
PsiElement(Py:IDENTIFIER)('say_whee')
|
||||
PyParameterList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PsiElement(Py:COLON)(':')
|
||||
PsiWhiteSpace('\n ')
|
||||
PyStatementList
|
||||
PyExpressionStatement
|
||||
PyCallExpression: print
|
||||
PyReferenceExpression: print
|
||||
PsiElement(Py:IDENTIFIER)('print')
|
||||
PyArgumentList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PyStringLiteralExpression: Whee!
|
||||
PsiElement(Py:SINGLE_QUOTED_STRING)('"Whee!"')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PsiWhiteSpace('\n\n\n')
|
||||
PyFunction('null')
|
||||
PyDecoratorList
|
||||
PyDecorator: @y
|
||||
PsiElement(Py:AT)('@')
|
||||
PyReferenceExpression: y
|
||||
PsiElement(Py:IDENTIFIER)('y')
|
||||
PsiErrorElement:Statement break expected
|
||||
<empty list>
|
||||
PyParameterList
|
||||
<empty list>
|
||||
PyStatementList
|
||||
<empty list>
|
||||
PsiElement(Py:COMMA)(',')
|
||||
PsiErrorElement:Statement expected, found Py:COMMA
|
||||
<empty list>
|
||||
PyExpressionStatement
|
||||
PyReferenceExpression: x
|
||||
PsiElement(Py:IDENTIFIER)('x')
|
||||
PsiWhiteSpace('\n')
|
||||
PyFunction('say_whee2')
|
||||
PsiElement(Py:DEF_KEYWORD)('def')
|
||||
PsiWhiteSpace(' ')
|
||||
PsiElement(Py:IDENTIFIER)('say_whee2')
|
||||
PyParameterList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
PsiElement(Py:COLON)(':')
|
||||
PsiWhiteSpace('\n ')
|
||||
PyStatementList
|
||||
PyExpressionStatement
|
||||
PyCallExpression: print
|
||||
PyReferenceExpression: print
|
||||
PsiElement(Py:IDENTIFIER)('print')
|
||||
PyArgumentList
|
||||
PsiElement(Py:LPAR)('(')
|
||||
PyStringLiteralExpression: Whee!
|
||||
PsiElement(Py:SINGLE_QUOTED_STRING)('"Whee!"')
|
||||
PsiElement(Py:RPAR)(')')
|
||||
@@ -45,7 +45,7 @@ public class PyDecoratorTest extends PyTestCase {
|
||||
}
|
||||
|
||||
public void testDecoParamCall() {
|
||||
PsiElement targetElement = find().getParent();
|
||||
PsiElement targetElement = find().getParent().getParent(); //first parent is PyCallExpression
|
||||
assertTrue(targetElement instanceof PyDecorator);
|
||||
PyDecorator deco = (PyDecorator)targetElement;
|
||||
PyFunction decofun = deco.getTarget();
|
||||
|
||||
@@ -369,6 +369,11 @@ public class PythonHighlightingTest extends PyTestCase {
|
||||
doTest(true, true);
|
||||
}
|
||||
|
||||
// PY-41305
|
||||
public void testExpressionAsDecorator() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), this::doTest);
|
||||
}
|
||||
|
||||
// PY-25381
|
||||
public void testBuiltinDecorator() {
|
||||
doTest(true, true);
|
||||
|
||||
@@ -257,6 +257,11 @@ public class PyCompatibilityInspectionTest extends PyInspectionTestCase {
|
||||
doTest(LanguageLevel.PYTHON38);
|
||||
}
|
||||
|
||||
// PY-41305
|
||||
public void testExpressionInDecorators() {
|
||||
doTest(LanguageLevel.PYTHON39);
|
||||
}
|
||||
|
||||
private void doTest(@NotNull LanguageLevel level) {
|
||||
runWithLanguageLevel(level, this::doTest);
|
||||
}
|
||||
|
||||
@@ -936,6 +936,11 @@ public class PythonParsingTest extends ParsingTestCase {
|
||||
doTest(LanguageLevel.PYTHON36);
|
||||
}
|
||||
|
||||
// PY-41305
|
||||
public void testExpressionsInDecorators() {
|
||||
doTest(LanguageLevel.getLatest());
|
||||
}
|
||||
|
||||
public void doTest() {
|
||||
doTest(LanguageLevel.PYTHON26);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user