From 3bef23669fb5a56c7093b8bb0f0d4976ca6a9094 Mon Sep 17 00:00:00 2001 From: David Peter Date: Mon, 11 Nov 2024 20:26:01 +0100 Subject: [PATCH] [red-knot] Diagnostic for possibly unbound imports (#14281) ## Summary This adds a new diagnostic when possibly unbound symbols are imported. The `TODO` comment had a question mark, do I'm not sure if this is really something that we want. This does not touch the un*declared* case, yet. relates to: #14022 ## Test Plan Updated already existing tests with new diagnostics --- .../resources/mdtest/import/conditional.md | 2 ++ .../resources/mdtest/narrow/issubclass.md | 3 +++ .../resources/mdtest/unary/not.md | 2 -- .../src/types/infer.rs | 23 ++++++++++++------- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md index d40efc6356..23056cbf80 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md @@ -21,6 +21,7 @@ reveal_type(y) ``` ```py +# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound` is possibly unbound" from maybe_unbound import x, y reveal_type(x) # revealed: Literal[3] @@ -50,6 +51,7 @@ reveal_type(y) Importing an annotated name prefers the declared type over the inferred type: ```py +# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound_annotated` is possibly unbound" from maybe_unbound_annotated import x, y reveal_type(x) # revealed: Literal[3] diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md index 43a18db0b1..44184f634c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md @@ -102,6 +102,9 @@ else: ### Handling of `None` ```py +# TODO: this error should ideally go away once we (1) understand `sys.version_info` branches, +# and (2) set the target Python version for this test to 3.10. +# error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound" from types import NoneType def flag() -> bool: ... diff --git a/crates/red_knot_python_semantic/resources/mdtest/unary/not.md b/crates/red_knot_python_semantic/resources/mdtest/unary/not.md index bb9f6ccac7..f43b486c24 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/unary/not.md +++ b/crates/red_knot_python_semantic/resources/mdtest/unary/not.md @@ -10,8 +10,6 @@ reveal_type(not not None) # revealed: Literal[False] ## Function ```py -from typing import reveal_type - def f(): return 1 diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index e7b4116395..def4f99a21 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -2008,20 +2008,27 @@ impl<'db> TypeInferenceBuilder<'db> { asname: _, } = alias; - // For possibly-unbound names, just eliminate Unbound from the type; we - // must be in a bound path. TODO diagnostic for possibly-unbound import? - module_ty - .member(self.db, &ast::name::Name::new(&name.id)) - .ignore_possibly_unbound() - .unwrap_or_else(|| { + match module_ty.member(self.db, &ast::name::Name::new(&name.id)) { + Symbol::Type(ty, boundness) => { + if boundness == Boundness::PossiblyUnbound { + self.diagnostics.add( + AnyNodeRef::Alias(alias), + "possibly-unbound-import", + format_args!("Member `{name}` of module `{module_name}` is possibly unbound",), + ); + } + + ty + } + Symbol::Unbound => { self.diagnostics.add( AnyNodeRef::Alias(alias), "unresolved-import", format_args!("Module `{module_name}` has no member `{name}`",), ); - Type::Unknown - }) + } + } } else { self.diagnostics .add_unresolved_module(import_from, *level, module);