diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/relative.md b/crates/red_knot_python_semantic/resources/mdtest/import/relative.md index cc34cbaf3e..f21c470646 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/relative.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/relative.md @@ -218,3 +218,33 @@ import package # error: [unresolved-attribute] "Type `` has no attribute `foo`" reveal_type(package.foo.X) # revealed: Unknown ``` + +## In the src-root + +`parser.py`: + +```py +X: int = 42 +``` + +`__main__.py`: + +```py +from .parser import X + +reveal_type(X) # revealed: int +``` + +## Beyond the src-root + +`parser.py`: + +```py +X: int = 42 +``` + +`__main__.py`: + +```py +from ..parser import X # error: [unresolved-import] +``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index da4793d7e7..8768c056c5 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -2513,18 +2513,32 @@ impl<'db> TypeInferenceBuilder<'db> { .ok_or(ModuleNameResolutionError::UnknownCurrentModule)?; let mut level = level.get(); if module.kind().is_package() { - level -= 1; + level = level.saturating_sub(1); } + let mut module_name = module.name().clone(); - for _ in 0..level { - module_name = module_name - .parent() - .ok_or(ModuleNameResolutionError::TooManyDots)?; + 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); + } } + if let Some(tail) = tail { - let tail = ModuleName::new(tail).ok_or(ModuleNameResolutionError::InvalidSyntax)?; module_name.extend(&tail); } + Ok(module_name) }