mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-20 04:37:57 +03:00
Changed import logic to better match behavior of python loader. If there's an empty directory and a python file with the same name, the import resolution logic now selects the file rather than failing resolution.
This commit is contained in:
parent
f71e1321bb
commit
d79e43b8e0
@ -601,6 +601,7 @@ export class ImportResolver {
|
||||
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
|
||||
} else {
|
||||
for (let i = 0; i < moduleDescriptor.nameParts.length; i++) {
|
||||
const isLastPart = i === moduleDescriptor.nameParts.length - 1;
|
||||
dirPath = combinePaths(dirPath, moduleDescriptor.nameParts[i]);
|
||||
let foundDirectory = false;
|
||||
|
||||
@ -620,60 +621,72 @@ export class ImportResolver {
|
||||
foundDirectory = fs.existsSync(dirPath) && isDirectory(dirPath);
|
||||
}
|
||||
|
||||
if (!foundDirectory) {
|
||||
importFailureInfo.push(`Could not find directory '${ dirPath }'`);
|
||||
if (foundDirectory) {
|
||||
if (!isLastPart) {
|
||||
// We are not at the last part, and we found a directory,
|
||||
// so continue to look for the next part.
|
||||
resolvedPaths.push(dirPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We weren't able to find the subdirectory. See if we can
|
||||
// find a ".py" or ".pyi" file with this name.
|
||||
const pyFilePath = stripTrailingDirectorySeparator(dirPath) + '.py';
|
||||
// See if we can find an __init__.py[i] in this directory.
|
||||
const pyFilePath = combinePaths(dirPath, '__init__.py');
|
||||
const pyiFilePath = pyFilePath + 'i';
|
||||
const pydFilePath = pyFilePath + 'd';
|
||||
let foundInit = false;
|
||||
|
||||
if (fs.existsSync(pyiFilePath) && isFile(pyiFilePath)) {
|
||||
importFailureInfo.push(`Resolved import with file '${ pyiFilePath }'`);
|
||||
resolvedPaths.push(pyiFilePath);
|
||||
if (i === moduleDescriptor.nameParts.length - 1) {
|
||||
if (isLastPart) {
|
||||
isStubFile = true;
|
||||
}
|
||||
foundInit = true;
|
||||
} else if (fs.existsSync(pyFilePath) && isFile(pyFilePath)) {
|
||||
importFailureInfo.push(`Resolved import with file '${ pyFilePath }'`);
|
||||
resolvedPaths.push(pyFilePath);
|
||||
} else if (allowPydFile && fs.existsSync(pydFilePath) && isFile(pydFilePath)) {
|
||||
importFailureInfo.push(`Resolved import with file '${ pydFilePath }'`);
|
||||
resolvedPaths.push(pydFilePath);
|
||||
if (i === moduleDescriptor.nameParts.length - 1) {
|
||||
isPydFile = true;
|
||||
}
|
||||
} else {
|
||||
importFailureInfo.push(`Did not find file '${ pyiFilePath }',` +
|
||||
` '${ pyFilePath }' or '${ pydFilePath }'`);
|
||||
foundInit = true;
|
||||
}
|
||||
|
||||
if (foundInit) {
|
||||
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const pyFilePath = combinePaths(dirPath, '__init__.py');
|
||||
// We weren't able to find a directory or we found a directory with
|
||||
// no __init__.py[i] file. See if we can find a ".py" or ".pyi" file
|
||||
// with this name.
|
||||
const pyFilePath = stripTrailingDirectorySeparator(dirPath) + '.py';
|
||||
const pyiFilePath = pyFilePath + 'i';
|
||||
const pydFilePath = pyFilePath + 'd';
|
||||
|
||||
if (fs.existsSync(pyiFilePath) && isFile(pyiFilePath)) {
|
||||
importFailureInfo.push(`Resolved import with file '${ pyiFilePath }'`);
|
||||
resolvedPaths.push(pyiFilePath);
|
||||
if (i === moduleDescriptor.nameParts.length - 1) {
|
||||
if (isLastPart) {
|
||||
isStubFile = true;
|
||||
}
|
||||
} else if (fs.existsSync(pyFilePath) && isFile(pyFilePath)) {
|
||||
importFailureInfo.push(`Resolved import with file '${ pyFilePath }'`);
|
||||
resolvedPaths.push(pyFilePath);
|
||||
} else {
|
||||
} else if (allowPydFile && fs.existsSync(pydFilePath) && isFile(pydFilePath)) {
|
||||
importFailureInfo.push(`Resolved import with file '${ pydFilePath }'`);
|
||||
resolvedPaths.push(pydFilePath);
|
||||
if (isLastPart) {
|
||||
isPydFile = true;
|
||||
}
|
||||
} else if (foundDirectory) {
|
||||
importFailureInfo.push(`Partially resolved import with directory '${ dirPath }'`);
|
||||
resolvedPaths.push('');
|
||||
if (i === moduleDescriptor.nameParts.length - 1) {
|
||||
if (isLastPart) {
|
||||
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
|
||||
isNamespacePackage = true;
|
||||
}
|
||||
} else {
|
||||
importFailureInfo.push(`Did not find file '${ pyiFilePath }',` +
|
||||
` '${ pyFilePath }' or '${ pydFilePath }'`);
|
||||
}
|
||||
|
||||
if (i === moduleDescriptor.nameParts.length - 1) {
|
||||
implicitImports = this._findImplicitImports(dirPath, [pyFilePath, pyiFilePath]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -828,6 +828,11 @@ test('Import1', () => {
|
||||
validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('Import2', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['import2.py']);
|
||||
validateResults(analysisResults, 2);
|
||||
});
|
||||
|
||||
test('Overload1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['overload1.py']);
|
||||
validateResults(analysisResults, 2);
|
||||
|
27
server/src/tests/samples/import2.py
Normal file
27
server/src/tests/samples/import2.py
Normal file
@ -0,0 +1,27 @@
|
||||
# This sample tests import resolution for relative imports.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import .package1 as p1
|
||||
a = p1.foo()
|
||||
|
||||
from .package1 import foo
|
||||
b = foo()
|
||||
|
||||
# This should generate an error because there is no
|
||||
# directory or file named package2.
|
||||
import .package2 as p2
|
||||
|
||||
# This should generate an error too.
|
||||
from .package2 import foo
|
||||
|
||||
|
||||
from .package1.sub import subfoo
|
||||
# subfoo should resolve to the package1/sub/__init__.py,
|
||||
# which returns a datetime. Verify that it does.
|
||||
c: datetime = subfoo()
|
||||
|
||||
from .package1.psyche import psyche1
|
||||
# This should resolve to package1/psyche.py even though
|
||||
# there is a package1/psyche directory present.
|
||||
d: int = psyche1()
|
3
server/src/tests/samples/package1/__init__.py
Normal file
3
server/src/tests/samples/package1/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
def foo():
|
||||
pass
|
4
server/src/tests/samples/package1/psyche.py
Normal file
4
server/src/tests/samples/package1/psyche.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
def psyche1() -> int:
|
||||
return 3
|
||||
|
4
server/src/tests/samples/package1/psyche/pysche.py
Normal file
4
server/src/tests/samples/package1/psyche/pysche.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
def psyche1() -> str:
|
||||
return "3"
|
||||
|
4
server/src/tests/samples/package1/sub.py
Normal file
4
server/src/tests/samples/package1/sub.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
def subfoo() -> str:
|
||||
return 'hello'
|
||||
|
2
server/src/tests/samples/package1/sub.pyi
Normal file
2
server/src/tests/samples/package1/sub.pyi
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
def subfoo() -> int: ...
|
5
server/src/tests/samples/package1/sub/__init__.py
Normal file
5
server/src/tests/samples/package1/sub/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
def subfoo() -> datetime:
|
||||
return datetime.now()
|
Loading…
Reference in New Issue
Block a user