Skip to content

Mention implicit export on missing attribute access #13917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 32 additions & 21 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,32 +408,43 @@ def has_no_attr(
if not self.are_type_names_disabled():
failed = False
if isinstance(original_type, Instance) and original_type.type.names:
alternatives = set(original_type.type.names.keys())

if module_symbol_table is not None:
alternatives |= {key for key in module_symbol_table.keys()}

# in some situations, the member is in the alternatives set
# but since we're in this function, we shouldn't suggest it
if member in alternatives:
alternatives.remove(member)

matches = [m for m in COMMON_MISTAKES.get(member, []) if m in alternatives]
matches.extend(best_matches(member, alternatives)[:3])
if member == "__aiter__" and matches == ["__iter__"]:
matches = [] # Avoid misleading suggestion
if matches:
if (
module_symbol_table is not None
and member in module_symbol_table
and not module_symbol_table[member].module_public
):
self.fail(
'{} has no attribute "{}"; maybe {}?{}'.format(
format_type(original_type),
member,
pretty_seq(matches, "or"),
extra,
),
f"{format_type(original_type, module_names=True)} does not "
f'explicitly export attribute "{member}"',
context,
code=codes.ATTR_DEFINED,
)
failed = True
else:
alternatives = set(original_type.type.names.keys())
if module_symbol_table is not None:
alternatives |= {
k for k, v in module_symbol_table.items() if v.module_public
}
# Rare but possible, see e.g. testNewAnalyzerCyclicDefinitionCrossModule
alternatives.discard(member)

matches = [m for m in COMMON_MISTAKES.get(member, []) if m in alternatives]
matches.extend(best_matches(member, alternatives)[:3])
if member == "__aiter__" and matches == ["__iter__"]:
matches = [] # Avoid misleading suggestion
if matches:
self.fail(
'{} has no attribute "{}"; maybe {}?{}'.format(
format_type(original_type),
member,
pretty_seq(matches, "or"),
extra,
),
context,
code=codes.ATTR_DEFINED,
)
failed = True
if not failed:
self.fail(
'{} has no attribute "{}"{}'.format(
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -1833,7 +1833,7 @@ class C:
import stub

reveal_type(stub.y) # N: Revealed type is "builtins.int"
reveal_type(stub.z) # E: Module has no attribute "z" \
reveal_type(stub.z) # E: "Module stub" does not explicitly export attribute "z" \
# N: Revealed type is "Any"

[file stub.pyi]
Expand Down Expand Up @@ -1925,7 +1925,7 @@ import mod
from mod import C, D # E: Module "mod" has no attribute "C"

reveal_type(mod.x) # N: Revealed type is "mod.submod.C"
mod.C # E: Module has no attribute "C"
mod.C # E: "Module mod" does not explicitly export attribute "C"
y = mod.D()
reveal_type(y.a) # N: Revealed type is "builtins.str"

Expand Down