[red-knot] Partial revert of relative import handling for files in the root of a search path (#16001)

## Summary

This PR reverts the behavior changes from
https://github.com/astral-sh/ruff/pull/15990

But it isn't just a revert, it also:

* Adds a test covering this specific behavior
* Preserves the improvement to use `saturating_sub` in the package case
to avoid overflows in the case of invalid syntax
* Use `ancestors` instead of a `for` loop

## Test Plan

Added test
This commit is contained in:
Micha Reiser 2025-02-07 10:04:09 +00:00 committed by GitHub
parent 26c37b1e0e
commit 38351e00ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 13 additions and 36 deletions

View file

@ -2512,30 +2512,19 @@ impl<'db> TypeInferenceBuilder<'db> {
let module = file_to_module(self.db(), self.file())
.ok_or(ModuleNameResolutionError::UnknownCurrentModule)?;
let mut level = level.get();
if module.kind().is_package() {
level = level.saturating_sub(1);
}
let mut module_name = module.name().clone();
let tail = tail
.map(|tail| ModuleName::new(tail).ok_or(ModuleNameResolutionError::InvalidSyntax))
.transpose()?;
for remaining_dots in (0..level).rev() {
if let Some(parent) = module_name.parent() {
module_name = parent;
} else if remaining_dots == 0 {
// If we reached a search path root and this was the last dot return the tail if any.
// If there's no tail, then we have a relative import that's too deep.
return tail.ok_or(ModuleNameResolutionError::TooManyDots);
} else {
// We're at a search path root. This is a too deep relative import if there's more than
// one dot remaining.
return Err(ModuleNameResolutionError::TooManyDots);
}
}
let mut module_name = module
.name()
.ancestors()
.nth(level as usize)
.ok_or(ModuleNameResolutionError::TooManyDots)?;
if let Some(tail) = tail {
let tail = ModuleName::new(tail).ok_or(ModuleNameResolutionError::InvalidSyntax)?;
module_name.extend(&tail);
}