mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-30 10:20:15 +07:00
PY-48012 Resolve and complete attributes in PEP 634 class patterns
Both are implemented other the type of the corresponding class. References resolve to any readable attribute of a class, however some obviously wrong variants such as special "dunder" attributes and methods are intentionally excluded from completion suggestions. GitOrigin-RevId: 5edac14f47cba39840b15b0dd7f21e2e46077261
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d15bf8a552
commit
1421975c96
@@ -1,5 +1,10 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface PyClassPattern extends PyPattern {
|
||||
@NotNull PyReferenceExpression getClassNameReference();
|
||||
|
||||
@NotNull PyPatternArgumentList getArgumentList();
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ public abstract class PyElementGenerator {
|
||||
|
||||
@NotNull
|
||||
public abstract PyExpression createExpressionFromText(@NotNull LanguageLevel languageLevel, @NotNull String text) throws IncorrectOperationException;
|
||||
|
||||
@NotNull
|
||||
public abstract PyPattern createPatternFromText(@NotNull LanguageLevel languageLevel, @NotNull String text) throws IncorrectOperationException;
|
||||
|
||||
/**
|
||||
* Adds elements to list inserting required commas.
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface PyKeywordPattern extends PyPattern {
|
||||
@NotNull String getKeyword();
|
||||
|
||||
@NotNull PsiElement getKeywordElement();
|
||||
|
||||
@Nullable PyPattern getValuePattern();
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
implementationClass="com.jetbrains.python.psi.impl.PyStringLiteralExpressionManipulator"/>
|
||||
<lang.elementManipulator forClass="com.jetbrains.python.psi.PyKeywordArgument"
|
||||
implementationClass="com.jetbrains.python.psi.impl.PyKeywordArgumentManipulator"/>
|
||||
<lang.elementManipulator forClass="com.jetbrains.python.psi.PyKeywordPattern"
|
||||
implementationClass="com.jetbrains.python.psi.impl.references.PyKeywordPatternManipulator"/>
|
||||
<lang.parserDefinition language="Python" implementationClass="com.jetbrains.python.PythonParserDefinition"/>
|
||||
|
||||
<!-- PyFunctionTypeAnnotation -->
|
||||
|
||||
@@ -6,21 +6,28 @@ import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.lookup.Lookup;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class OverwriteEqualsInsertHandler implements InsertHandler<LookupElement> {
|
||||
public static OverwriteEqualsInsertHandler INSTANCE = new OverwriteEqualsInsertHandler();
|
||||
public final static OverwriteEqualsInsertHandler INSTANCE = new OverwriteEqualsInsertHandler();
|
||||
|
||||
private OverwriteEqualsInsertHandler() { }
|
||||
|
||||
@Override
|
||||
public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) {
|
||||
if (context.getCompletionChar() != Lookup.REPLACE_SELECT_CHAR) {
|
||||
return;
|
||||
}
|
||||
String lookupString = item.getLookupString();
|
||||
Document doc = context.getDocument();
|
||||
int tailOffset = context.getTailOffset();
|
||||
if (tailOffset < doc.getCharsSequence().length() && doc.getCharsSequence().charAt(tailOffset) == '=') {
|
||||
doc.deleteString(tailOffset, tailOffset+1);
|
||||
if (lookupString.endsWith("=") && CharArrayUtil.regionMatches(doc.getCharsSequence(), tailOffset, "=")) {
|
||||
doc.deleteString(tailOffset, tailOffset + 1);
|
||||
}
|
||||
else if (lookupString.endsWith(" = ") && CharArrayUtil.regionMatches(doc.getCharsSequence(), tailOffset, " = ")) {
|
||||
doc.deleteString(tailOffset, tailOffset + 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
|
||||
final String text = element.getText();
|
||||
TextRange rangeInElement = reference.getRangeInElement();
|
||||
String refText = text; // text of the part we're working with
|
||||
if (rangeInElement.getStartOffset() > 0 && rangeInElement.getEndOffset() > 0) {
|
||||
if (rangeInElement.getStartOffset() >= 0 && rangeInElement.getEndOffset() > 0) {
|
||||
refText = rangeInElement.substring(text);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.jetbrains.python.psi.impl;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.jetbrains.python.psi.PyClassPattern;
|
||||
import com.jetbrains.python.psi.PyElementVisitor;
|
||||
import com.jetbrains.python.psi.PyPatternArgumentList;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PyClassPatternImpl extends PyElementImpl implements PyClassPattern {
|
||||
public PyClassPatternImpl(ASTNode astNode) {
|
||||
@@ -13,4 +18,14 @@ public class PyClassPatternImpl extends PyElementImpl implements PyClassPattern
|
||||
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
|
||||
pyVisitor.visitPyClassPattern(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PyReferenceExpression getClassNameReference() {
|
||||
return Objects.requireNonNull(findChildByClass(PyReferenceExpression.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PyPatternArgumentList getArgumentList() {
|
||||
return Objects.requireNonNull(findChildByClass(PyPatternArgumentList.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +274,16 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
|
||||
throw new IncorrectOperationException("could not parse text as expression: " + text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PyPattern createPatternFromText(@NotNull LanguageLevel languageLevel, @NotNull String text)
|
||||
throws IncorrectOperationException {
|
||||
String matchStatement = "match x:\n" +
|
||||
" case C(" + text + "):\n" +
|
||||
" pass ";
|
||||
int[] pathToAttrPattern = {0, 5, 2, 1, 1};
|
||||
return createFromText(languageLevel, PyPattern.class, matchStatement, pathToAttrPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PyCallExpression createCallExpression(final LanguageLevel langLevel, String functionName) {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.jetbrains.python.psi.PyElementVisitor;
|
||||
import com.jetbrains.python.psi.PyKeywordPattern;
|
||||
import com.jetbrains.python.psi.PyPattern;
|
||||
import com.jetbrains.python.psi.impl.references.PyKeywordPatternReference;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PyKeywordPatternImpl extends PyElementImpl implements PyKeywordPattern {
|
||||
public PyKeywordPatternImpl(ASTNode astNode) {
|
||||
@@ -13,4 +19,24 @@ public class PyKeywordPatternImpl extends PyElementImpl implements PyKeywordPatt
|
||||
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
|
||||
pyVisitor.visitPyKeywordPattern(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getKeyword() {
|
||||
return getKeywordElement().getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElement getKeywordElement() {
|
||||
return getFirstChild();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PyPattern getValuePattern() {
|
||||
return findChildByClass(PyPattern.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiReference getReference() {
|
||||
return new PyKeywordPatternReference(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.jetbrains.python.psi.impl.references;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.AbstractElementManipulator;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.psi.PyElementGenerator;
|
||||
import com.jetbrains.python.psi.PyKeywordPattern;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class PyKeywordPatternManipulator extends AbstractElementManipulator<PyKeywordPattern> {
|
||||
@Override
|
||||
public @Nullable PyKeywordPattern handleContentChange(@NotNull PyKeywordPattern element,
|
||||
@NotNull TextRange range,
|
||||
String newContent) throws IncorrectOperationException {
|
||||
if (element.getKeywordElement().getTextRangeInParent().equals(range)) {
|
||||
PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject());
|
||||
String newPatternText = newContent + "=None";
|
||||
PyKeywordPattern newPattern = (PyKeywordPattern)generator.createPatternFromText(LanguageLevel.forElement(element), newPatternText);
|
||||
element.getKeywordElement().replace(newPattern.getKeywordElement());
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.jetbrains.python.psi.impl.references;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementDecorator;
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReferenceBase;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PythonCodeStyleService;
|
||||
import com.jetbrains.python.codeInsight.completion.OverwriteEqualsInsertHandler;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.types.PyInstantiableType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.PyTypeUtil;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
public final class PyKeywordPatternReference extends PsiReferenceBase.Poly<PyKeywordPattern> {
|
||||
public PyKeywordPatternReference(@NotNull PyKeywordPattern keywordPattern) {
|
||||
super(keywordPattern, keywordPattern.getKeywordElement().getTextRangeInParent(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
|
||||
PyClassPattern classPattern = getContainingClassPattern();
|
||||
if (classPattern == null) {
|
||||
return ResolveResult.EMPTY_ARRAY;
|
||||
}
|
||||
PyKeywordPattern keywordPattern = getElement();
|
||||
TypeEvalContext typeContext = TypeEvalContext.codeInsightFallback(keywordPattern.getProject());
|
||||
PyResolveContext resolveContext = PyResolveContext.defaultContext(typeContext);
|
||||
return StreamEx.of(resolveToClassTypes(classPattern, resolveContext))
|
||||
.flatMap(t -> StreamEx.of(ContainerUtil.notNullize(t.resolveMember(keywordPattern.getKeyword(),
|
||||
null, AccessDirection.READ, resolveContext))))
|
||||
.toArray(ResolveResult.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @NotNull [] getVariants() {
|
||||
PyClassPattern classPattern = getContainingClassPattern();
|
||||
if (classPattern == null) {
|
||||
return LookupElement.EMPTY_ARRAY;
|
||||
}
|
||||
PyKeywordPattern keywordPattern = getElement();
|
||||
TypeEvalContext typeContext = TypeEvalContext.codeCompletion(keywordPattern.getProject(), keywordPattern.getContainingFile());
|
||||
return collectClassAttributeVariants(getElement(), classPattern, typeContext);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PyClassPattern getContainingClassPattern() {
|
||||
return as(getElement().getParent().getParent(), PyClassPattern.class);
|
||||
}
|
||||
|
||||
static LookupElement @NotNull [] collectClassAttributeVariants(@NotNull PsiElement location,
|
||||
@NotNull PyClassPattern classPattern,
|
||||
@NotNull TypeEvalContext typeContext) {
|
||||
PyResolveContext resolveContext = PyResolveContext.defaultContext(typeContext);
|
||||
return StreamEx.of(resolveToClassTypes(classPattern, resolveContext))
|
||||
.flatMap(t -> StreamEx.of(t.getCompletionVariants("", location, new ProcessingContext())))
|
||||
.select(LookupElement.class)
|
||||
.filter(e -> isMeaningfulClassPatternAttribute(e))
|
||||
.map(e -> new KeywordAttributeLookupDecorator(e, location))
|
||||
.toArray(LookupElement.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
private static boolean isMeaningfulClassPatternAttribute(@NotNull LookupElement lookupElement) {
|
||||
String lookupString = lookupElement.getLookupString();
|
||||
if (lookupString.contains(".") || PyUtil.isSpecialName(lookupString)) {
|
||||
return false;
|
||||
}
|
||||
PsiElement elem = lookupElement.getPsiElement();
|
||||
if (elem instanceof PyClass) {
|
||||
return false;
|
||||
}
|
||||
if (elem instanceof PyFunction && ((PyFunction)elem).getProperty() == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType resolveToClassTypes(@NotNull PyClassPattern classPattern, @NotNull PyResolveContext resolveContext) {
|
||||
List<PsiElement> elements = PyUtil.multiResolveTopPriority(classPattern.getClassNameReference(), resolveContext);
|
||||
return StreamEx.of(elements)
|
||||
.select(PyClass.class)
|
||||
.map(e -> e.getType(resolveContext.getTypeEvalContext()))
|
||||
.nonNull()
|
||||
.map(PyInstantiableType::toInstance)
|
||||
.collect(PyTypeUtil.toUnion());
|
||||
}
|
||||
|
||||
private static class KeywordAttributeLookupDecorator extends LookupElementDecorator<LookupElement> {
|
||||
private final boolean myAddSpacesAroundEq;
|
||||
|
||||
private KeywordAttributeLookupDecorator(@NotNull LookupElement e, @NotNull PsiElement settingsAnchor) {
|
||||
super(e);
|
||||
myAddSpacesAroundEq = PythonCodeStyleService.getInstance().isSpaceAroundEqInKeywordArgument(settingsAnchor.getContainingFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getLookupString() {
|
||||
return super.getLookupString() + (myAddSpacesAroundEq ? " = " : "=");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAllLookupStrings() {
|
||||
return Collections.singleton(getLookupString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderElement(LookupElementPresentation presentation) {
|
||||
super.renderElement(presentation);
|
||||
presentation.setItemText(getLookupString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInsert(@NotNull InsertionContext context) {
|
||||
OverwriteEqualsInsertHandler.INSTANCE.handleInsert(context, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,13 @@ import com.intellij.psi.PsiElementResolveResult;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.PyImportElement;
|
||||
import com.jetbrains.python.psi.PyQualifiedExpression;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
|
||||
public class PyTargetReference extends PyReferenceImpl {
|
||||
@@ -49,6 +50,20 @@ public class PyTargetReference extends PyReferenceImpl {
|
||||
if (importElement != null && myElement == importElement.getAsNameElement()) {
|
||||
return ArrayUtilRt.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
PyClassPattern classPattern = getContainingClassPatternIfInAttributeNamePosition();
|
||||
if (classPattern != null) {
|
||||
TypeEvalContext context = TypeEvalContext.codeCompletion(myElement.getProject(), myElement.getContainingFile());
|
||||
return PyKeywordPatternReference.collectClassAttributeVariants(myElement, classPattern, context);
|
||||
}
|
||||
return super.getVariants();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PyClassPattern getContainingClassPatternIfInAttributeNamePosition() {
|
||||
PsiElement grandParent = myElement.getParent().getParent();
|
||||
if (myElement.getParent() instanceof PyCapturePattern && grandParent instanceof PyPatternArgumentList) {
|
||||
return as(grandParent.getParent(), PyClassPattern.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import dataclasses
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class C:
|
||||
count: int
|
||||
name: str
|
||||
|
||||
|
||||
match C(count=42, name='foo'):
|
||||
case C(<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,11 @@
|
||||
import typing
|
||||
|
||||
|
||||
class C(typing.NamedTuple):
|
||||
count: int
|
||||
name: str
|
||||
|
||||
|
||||
match C(count=42, name='foo'):
|
||||
case C(<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
class_attr = 42
|
||||
|
||||
match C():
|
||||
case C(class_attr=<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
class_attr = 42
|
||||
|
||||
match C():
|
||||
case C(cla<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.__attr = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(__<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.__attr = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(__<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
attr = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr = 42
|
||||
|
||||
match C():
|
||||
case C(instance_attr=<caret>=True):
|
||||
pass
|
||||
7
python/testData/completion/existingKeywordPattern.py
Normal file
7
python/testData/completion/existingKeywordPattern.py
Normal file
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr = 42
|
||||
|
||||
match C():
|
||||
case C(instance<caret>=True):
|
||||
pass
|
||||
@@ -0,0 +1,12 @@
|
||||
class Base:
|
||||
def __init__(self):
|
||||
self.inherited_attr = 42
|
||||
|
||||
|
||||
class C(Base):
|
||||
pass
|
||||
|
||||
|
||||
match C():
|
||||
case C(inherited_attr=<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,12 @@
|
||||
class Base:
|
||||
def __init__(self):
|
||||
self.inherited_attr = 42
|
||||
|
||||
|
||||
class C(Base):
|
||||
pass
|
||||
|
||||
|
||||
match C():
|
||||
case C(inh<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
class C:
|
||||
class InnerClass:
|
||||
pass
|
||||
|
||||
|
||||
match C():
|
||||
case C(Inner<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr = 42
|
||||
|
||||
match C():
|
||||
case C(instance_attr=<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.instance_attr = 42
|
||||
|
||||
match C():
|
||||
case C(ins<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
class C:
|
||||
def method(self):
|
||||
pass
|
||||
|
||||
|
||||
match C():
|
||||
case C(met<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
class C:
|
||||
@property
|
||||
def prop(self):
|
||||
pass
|
||||
|
||||
|
||||
match C():
|
||||
case C(prop=<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
class C:
|
||||
@property
|
||||
def prop(self):
|
||||
pass
|
||||
|
||||
|
||||
match C():
|
||||
case C(pro<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.attr = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(attr = <caret>):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.attr = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(attr<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
pass
|
||||
|
||||
match C():
|
||||
case C(__<caret>):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
attr1 = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(attr1=42):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
attr1 = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(att<caret>r2=42):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
attr1 = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(attr1 = <caret>42):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
attr1 = 42
|
||||
|
||||
|
||||
match C():
|
||||
case C(att<caret>r2 = 42):
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
class Class:
|
||||
def __init__(self, foo):
|
||||
self.foo = foo
|
||||
|
||||
|
||||
match Class(1, 2):
|
||||
case Class(<error descr="Unresolved reference 'baz'">baz</error>=42):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Class:
|
||||
def __init__(self, foo):
|
||||
self.foo = foo
|
||||
|
||||
|
||||
match Class(1, 2):
|
||||
case Class(f<caret>oo=42):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Class:
|
||||
def __init__(self, foo):
|
||||
self.bar = foo
|
||||
|
||||
|
||||
match Class(1, 2):
|
||||
case Class(bar=42):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Class:
|
||||
foo = 42
|
||||
|
||||
|
||||
match Class():
|
||||
case Class(foo=42):
|
||||
# <ref>
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class Base:
|
||||
foo = 42
|
||||
|
||||
|
||||
class Class(Base):
|
||||
pass
|
||||
|
||||
|
||||
match Class():
|
||||
case Class(foo=42):
|
||||
# <ref>
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class Base:
|
||||
def __init__(self, foo):
|
||||
self.foo = foo
|
||||
|
||||
|
||||
class Class(Base):
|
||||
pass
|
||||
|
||||
|
||||
match Class(1):
|
||||
case Class(foo=42):
|
||||
# <ref>
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
class Base:
|
||||
@property
|
||||
def foo(self):
|
||||
return 42
|
||||
|
||||
|
||||
class Class(Base):
|
||||
pass
|
||||
|
||||
|
||||
match Class():
|
||||
case Class(foo=42):
|
||||
# <ref>
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class Class:
|
||||
def __init__(self, foo):
|
||||
self.foo = foo
|
||||
|
||||
|
||||
match Class(1):
|
||||
case Class(foo=42):
|
||||
# <ref>
|
||||
pass
|
||||
|
||||
11
python/testData/resolve/KeywordPatternResolvesToProperty.py
Normal file
11
python/testData/resolve/KeywordPatternResolvesToProperty.py
Normal file
@@ -0,0 +1,11 @@
|
||||
class Class:
|
||||
@property
|
||||
def foo(self):
|
||||
return 42
|
||||
|
||||
|
||||
match Class():
|
||||
case Class(foo=42):
|
||||
# <ref>
|
||||
pass
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
|
||||
import com.intellij.codeInsight.lookup.Lookup;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
@@ -10,6 +11,7 @@ import com.intellij.testFramework.fixtures.TestLookupElementPresentation;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.codeInsight.completion.PyModuleNameCompletionContributor;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.formatter.PyCodeStyleSettings;
|
||||
import com.jetbrains.python.inspections.PyMethodParametersInspection;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.psi.types.PyNamedTupleType;
|
||||
@@ -62,6 +64,13 @@ public class Py3CompletionTest extends PyTestCase {
|
||||
myFixture.checkResultByFile(testName + ".after.py");
|
||||
}
|
||||
|
||||
private void doTabTest() {
|
||||
myFixture.configureByFile(getTestName(true) + ".py");
|
||||
myFixture.completeBasic();
|
||||
myFixture.finishLookup(Lookup.REPLACE_SELECT_CHAR);
|
||||
myFixture.checkResultByFile(getTestName(true) + ".after.py");
|
||||
}
|
||||
|
||||
public void doNegativeTest() {
|
||||
final String testName = getTestName(true);
|
||||
myFixture.configureByFile(testName + ".py");
|
||||
@@ -545,6 +554,102 @@ public class Py3CompletionTest extends PyTestCase {
|
||||
assertTrue(ContainerUtil.exists(variants, v -> v.getLookupString().equals("join")));
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testInstanceAttributesSuggestedForKeywordPatterns() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testClassAttributesSuggestedForKeywordPatterns() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testInheritedAttributesSuggestedForKeywordPatterns() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testPropertiesSuggestedForKeywordPatterns() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testExistingKeywordPattern() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testEqualSignRenderedInKeywordPatternVariant() {
|
||||
myFixture.configureByFile(getTestName(true) + ".py");
|
||||
LookupElement[] variants = myFixture.completeBasic();
|
||||
assertNotNull(variants);
|
||||
TestLookupElementPresentation presentation = new TestLookupElementPresentation();
|
||||
LookupElement attrVariant = ContainerUtil.find(variants, v -> v.getLookupString().equals("attr="));
|
||||
assertNotNull(attrVariant);
|
||||
attrVariant.renderElement(presentation);
|
||||
assertTrue(presentation.getItemText().endsWith("="));
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testTabCompletionOverridesEqualSignInKeywordPattern() {
|
||||
doTabTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testInnerClassesNotSuggestedForKeywordPatterns() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testMethodsNotSuggestedForKeywordPatterns() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testSpecialAttributesNotSuggestedForKeywordPatterns() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testClassPrivateAttributesNotSuggestedForKeywordPatterns() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testAttributesOfDataclassSuggestedForKeywordPatterns() {
|
||||
final String testName = getTestName(true);
|
||||
myFixture.configureByFile(testName + ".py");
|
||||
myFixture.completeBasic();
|
||||
List<String> variants = myFixture.getLookupElementStrings();
|
||||
assertContainsElements(variants, "count=", "name=");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testAttributesOfNamedTupleSuggestedForKeywordPatterns() {
|
||||
final String testName = getTestName(true);
|
||||
myFixture.configureByFile(testName + ".py");
|
||||
myFixture.completeBasic();
|
||||
List<String> variants = myFixture.getLookupElementStrings();
|
||||
assertContainsElements(variants, "count=", "name=");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testSpacesAroundEqualSignForKeywordPatternsIfEnabled() {
|
||||
getPythonCodeStyleSettings().SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testTabCompletionOverridesSpacesAroundEqualSignInKeywordPatternIfEnabled() {
|
||||
getPythonCodeStyleSettings().SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT = true;
|
||||
doTabTest();
|
||||
}
|
||||
|
||||
private @NotNull PyCodeStyleSettings getPythonCodeStyleSettings() {
|
||||
return getCodeStyleSettings().getCustomSettings(PyCodeStyleSettings.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return super.getTestDataPath() + "/completion";
|
||||
|
||||
@@ -826,4 +826,35 @@ public class Py3ResolveTest extends PyResolveTestCase {
|
||||
final PyFunction function = assertInstanceOf(ScopeUtil.getScopeOwner(target), PyFunction.class);
|
||||
assertEquals("g", function.getName());
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testKeywordPatternResolvesToInstanceAttribute() {
|
||||
assertResolvesTo(PyTargetExpression.class, "foo");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testKeywordPatternResolvesToClassAttribute() {
|
||||
assertResolvesTo(PyTargetExpression.class, "foo");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testKeywordPatternResolvesToProperty() {
|
||||
assertResolvesTo(PyFunction.class, "foo");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testKeywordPatternResolvesToInheritedInstanceAttribute() {
|
||||
assertResolvesTo(PyTargetExpression.class, "foo");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testKeywordPatternResolvesToInheritedClassAttribute() {
|
||||
assertResolvesTo(PyTargetExpression.class, "foo");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testKeywordPatternResolvesToInheritedProperty() {
|
||||
assertResolvesTo(PyFunction.class, "foo");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -886,6 +886,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testUnresolvedKeywordPattern() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
@@ -363,6 +363,11 @@ public class PyRenameTest extends PyTestCase {
|
||||
doMultiFileTest("bar.pyi");
|
||||
}
|
||||
|
||||
// PY-48012
|
||||
public void testRenameKeywordParameter() {
|
||||
doTest("bar");
|
||||
}
|
||||
|
||||
private void renameWithDocStringFormat(DocStringFormat format, final String newName) {
|
||||
runWithDocStringFormat(format, () -> doTest(newName));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user