support absolute imports

This commit is contained in:
Aria Desires
2025-11-05 23:08:14 -05:00
parent 33b942c7ad
commit 260edcff57
3 changed files with 40 additions and 21 deletions

View File

@@ -333,7 +333,7 @@ reveal_type(mypackage.nested.X) # revealed: Unknown
### In Non-Stub
`from mypackage.submodule import nested` in an `__init__.py` only creates `nested`.
`from mypackage.submodule import nested` in an `__init__.py` only creates `submodule` and `nested`.
`mypackage/__init__.py`:
@@ -357,12 +357,11 @@ X: int = 42
```py
import mypackage
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
# TODO: this would be nice to support
# error: "has no member `submodule`"
reveal_type(mypackage.submodule) # revealed: Unknown
# error: "has no member `submodule`"
# error: "has no member `nested`"
reveal_type(mypackage.submodule.nested) # revealed: Unknown
# error: "has no member `submodule`"
# error: "has no member `nested`"
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
reveal_type(mypackage.nested.X) # revealed: int

View File

@@ -1451,7 +1451,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
// If we see:
//
// * `from .x.y import z` (must be relative!)
// * `from .x.y import z` (or `from whatever.thispackage.x.y`)
// * And we are in an `__init__.py(i)` (hereafter `thispackage`)
// * And this is the first time we've seen `from .x` in this module
// * And we're in the global scope
@@ -1465,14 +1465,19 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
// reasons but it works well for most practical purposes. In particular it's nice
// that `x` can be freely overwritten, and that we don't assume that an import
// in one function is visible in another function.
//
// TODO: Also support `from thispackage.x.y import z`?
if self.current_scope() == FileScopeId::global()
&& node.level == 1
&& let Some(submodule) = &node.module
&& let Some(parsed_submodule) = ModuleName::new(submodule.as_str())
&& let Some(direct_submodule) = parsed_submodule.components().next()
if let Some(submodule) = &node.module
&& self.current_scope() == FileScopeId::global()
&& self.file.is_package(self.db)
&& let Ok(module_name) = ModuleName::from_identifier_parts(
self.db,
self.file,
node.module.as_deref(),
node.level,
)
&& let Ok(thispackage) =
ModuleName::from_identifier_parts(self.db, self.file, None, 1)
&& let Some(relative_submodule) = module_name.relative_to(&thispackage)
&& let Some(direct_submodule) = relative_submodule.components().next()
&& !self.seen_submodule_imports.contains(direct_submodule)
{
self.seen_submodule_imports

View File

@@ -5916,13 +5916,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// this type of Definition is only created when a `from..import` is in global scope.)
// Get this package's module by resolving `.`
let Ok(module_name) = ModuleName::from_identifier_parts(self.db(), self.file(), None, 1)
let Ok(thispackage_name) =
ModuleName::from_identifier_parts(self.db(), self.file(), None, 1)
else {
self.add_binding(import_from.into(), definition, |_, _| Type::unknown());
return;
};
let Some(module) = resolve_module(self.db(), &module_name) else {
let Some(module) = resolve_module(self.db(), &thispackage_name) else {
self.add_binding(import_from.into(), definition, |_, _| Type::unknown());
return;
};
@@ -5932,12 +5933,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
!submodule.is_empty(),
"ImportFromSubmoduleDefinitionKind constructed with empty module"
);
let name = submodule
.split_once('.')
.map(|(first, _)| first)
.unwrap_or(submodule.as_str());
let Ok(submodule_name) = ModuleName::from_identifier_parts(
self.db(),
self.file(),
import_from.module.as_deref(),
import_from.level,
) else {
self.add_binding(import_from.into(), definition, |_, _| Type::unknown());
return;
};
let Some(relative_submodule_name) = submodule_name.relative_to(&thispackage_name) else {
self.add_binding(import_from.into(), definition, |_, _| Type::unknown());
return;
};
let Some(name) = relative_submodule_name.components().next() else {
self.add_binding(import_from.into(), definition, |_, _| Type::unknown());
return;
};
let full_submodule_name = ModuleName::new(name).map(|final_part| {
let mut ret = module_name.clone();
let mut ret = thispackage_name.clone();
ret.extend(&final_part);
ret
});
@@ -5970,7 +5985,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
};
let diagnostic = builder.into_diagnostic(format_args!(
"Module `{module_name}` has no submodule `{name}`"
"Module `{thispackage_name}` has no submodule `{name}`"
));
if let Some(full_submodule_name) = full_submodule_name {