PY-77940 Allow resolving underscored names in .pyi stubs

This reverts the changes made in eb733982acf0992fbae822d0feb582fb07ad010f
for PY-40233.

The original problem was PyShadowingBuiltinsInspection erroneously reporting
redefining private names declared in builtins.pyi, such as _T, however,
resolving underscored names from all .pyi stubs was prohibited to fix that.

These are considered "stub-only objects" according to the typing spec
(https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#stub-only-objects),
but it doesn't prohibit reusing them in other stubs and in .py files under
an `if typing.TYPE_CHECKING:` guard.
Besides, both Mypy and Pyright resolve such names imported from .pyi stubs without warnings,
so it doesn't make sense to enforce additional visibility restrictions from our side.

Also, I enabled completing such names in other .pyi stubs (but not in .py files).
Previously, in PY-38172 their completion was disabled everywhere.

GitOrigin-RevId: 8340d4dcaf732d18d481bbee824ffdddd2719d3d
This commit is contained in:
Mikhail Golubev
2024-12-17 21:22:01 +02:00
committed by intellij-monorepo-bot
parent 300099d359
commit 820da7c49c
16 changed files with 75 additions and 7 deletions

View File

@@ -2007,9 +2007,11 @@ public abstract class PythonCommonCompletionTest extends PythonCommonTestCase {
}
// PY-38172
public void testNoPrivateStubElementsInModuleCompletion() {
PsiFile file = myFixture.configureByText(PythonFileType.INSTANCE, "import collections\n" +
"collections.<caret>");
public void testNoPrivateStubElementsInCompletionForCollectionsModule() {
PsiFile file = myFixture.configureByText(PythonFileType.INSTANCE, """
import collections
collections.<caret>
""");
myFixture.completeBasic();
List<String> suggested = myFixture.getLookupElementStrings();
assertNotEmpty(suggested);
@@ -2018,6 +2020,19 @@ public abstract class PythonCommonCompletionTest extends PythonCommonTestCase {
assertSdkRootsNotParsed(file);
}
// PY-38172
public void testPrivateStubElementsNotSuggestedInPyFiles() {
doMultiFileTest();
}
// PY-38172
public void testPrivateStubElementsSuggestedInOtherPyiStubs() {
myFixture.copyDirectoryToProject(getTestName(true), "");
myFixture.configureByFile("a.pyi");
myFixture.complete(CompletionType.BASIC, 1);
myFixture.checkResultByFile(getTestName(true) + "/a.after.pyi");
}
// PY-42520
public void testNoRepeatingNamedArgs() {
runWithLanguageLevel(

View File

@@ -152,7 +152,7 @@ public final class PyShadowingBuiltinsInspection extends PyInspection {
return;
}
final String name = element.getName();
if (name != null && !myIgnoredNames.contains(name)) {
if (name != null && PyUtil.getInitialUnderscores(name) != 1 && !myIgnoredNames.contains(name)) {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(element);
final PsiElement builtin = builtinCache.getByName(name);
if (builtin != null && !PyUtil.inSameFile(builtin, element)) {

View File

@@ -19,6 +19,7 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyQualifiedExpression;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.types.TypeEvalContext;
@@ -57,7 +58,7 @@ public final class PythonBuiltinReferenceResolveProvider implements PyReferenceR
// ...as a builtin symbol
final PyFile builtinsFile = builtinCache.getBuiltinsFile();
if (builtinsFile != null) {
if (builtinsFile != null && !PyUtil.isClassPrivateName(referencedName) && PyUtil.getInitialUnderscores(referencedName) != 1) {
for (RatedResolveResult resolveResult : builtinsFile.multiResolveName(referencedName)) {
result.add(new ImportedResolveResult(resolveResult.getElement(), resolveResult.getRate(), null));
}

View File

@@ -40,7 +40,6 @@ class PyiFile(viewProvider: FileViewProvider) : PyFileImpl(viewProvider, PyiLang
override fun multiResolveName(name: String, exported: Boolean): List<RatedResolveResult> {
if (name == "function" && PyBuiltinCache.getInstance(this).builtinsFile == this) return emptyList()
if (exported && isPrivateName(name)) return emptyList()
val baseResults = super.multiResolveName(name, exported)
val dunderAll = dunderAll ?: emptyList()
@@ -55,10 +54,17 @@ class PyiFile(viewProvider: FileViewProvider) : PyFileImpl(viewProvider, PyiLang
lastParent: PsiElement?,
place: PsiElement): Boolean {
val dunderAll = dunderAll ?: emptyList()
val completingInPyiStub = PyiUtil.isInsideStub(place)
val wrapper = object : DelegatingScopeProcessor(processor) {
override fun execute(element: PsiElement, state: ResolveState): Boolean = when {
// According to the typing spec, underscored names in .pyi stubs should be considered
// "stub-only" objects (https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#stub-only-objects).
// However, they can still be re-used between .pyi stubs and imported in .py files under typing.TYPE_CHECKING.
// Also, they can point to some inadvertently exposed de-facto API of some .py module.
// Therefore, we do allow resolving such names, but don't suggest them among completion variants
// to avoid polluting the lookup with internal names like "_T", "_Ts", etc.
!completingInPyiStub && element is PsiNamedElement && isPrivateName(element.name) -> true
isPrivateImport(element, dunderAll) -> true
element is PsiNamedElement && isPrivateName(element.name) -> true
else -> super.execute(element, state)
}
}

View File

@@ -0,0 +1,4 @@
import typing
if typing.TYPE_CHECKING:
from _common import _Place<caret>

View File

@@ -0,0 +1,4 @@
import typing
if typing.TYPE_CHECKING:
from _common import _Place<caret>

View File

@@ -0,0 +1,3 @@
from typing import TypeAlias
_Placeholder: TypeAlias = int

View File

@@ -0,0 +1 @@
from common import _Placeholder

View File

@@ -0,0 +1 @@
from common import _Place<caret>

View File

@@ -0,0 +1,3 @@
from typing import TypeAlias
_Placeholder: TypeAlias = int

View File

@@ -0,0 +1,4 @@
from common import _Placeholder
x: _Placeholder
# <ref>

View File

@@ -0,0 +1,4 @@
from typing import TypeAlias
_Placeholder: TypeAlias = int

View File

@@ -0,0 +1,4 @@
from typing import TypeAlias
_Placeholder: TypeAlias = int

View File

@@ -0,0 +1,4 @@
from common import _Placeholder
def f() -> _Placeholder: ...

View File

@@ -6214,6 +6214,15 @@ public class PyTypingTest extends PyTestCase {
""");
}
// PY-77940
public void testUnderscoredNameInPyiStub() {
doMultiFileStubAwareTest("int", """
from lib import f
expr = f()
""");
}
private void doTestNoInjectedText(@NotNull String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());

View File

@@ -81,4 +81,9 @@ public class PyiResolveTest extends PyMultiFileResolveTestCase {
QualifiedName internalCanonicalImportPath = QualifiedNameFinder.findCanonicalImportPath(internalClass, null);
assertEquals(QualifiedName.fromDottedString("pkg.mod"), internalCanonicalImportPath);
}
// PY-77940
public void testUnderscoredName() {
assertResolvesTo(PyTargetExpression.class, "_Placeholder");
}
}