Skip to content

Fix variables in __all__ being flagged by --no-implicit-reexport #7099

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

Closed
wants to merge 1 commit into from

Conversation

bluetech
Copy link
Contributor

The natural expectation is that when a variable is explicitly added to a
module's __all__, the module explicitly means to export it.

Previously, this was not the case, and a from [..] import [..] as [..]
reexport was required by --no-implicit-export even if the variable is
listed in __all__.

Fixes #7042

@bluetech
Copy link
Contributor Author

bluetech commented Jun 28, 2019

Following @JukkaL's suggestion in #7042 (comment), I tried to look at the code for a couple of hours and this is what I came up with. I am not sure it is correct at all, or if it has any unexpected consequences, so please review carefully (once/if it passes CI) :)

Copy link
Collaborator

@msullivan msullivan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks solid except for the one subtlety I mentioned.

if '__all__' in self.globals:
for name, g in self.globals.items():
if name not in self.all_exports:
if name in self.all_exports:
g.module_hidden = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the docs for module_public and module_hidden:

  • public means that it will be picked up by an import *
  • hidden means that it can't be imported at all

So I think we need to set public = True when the name does appear to prevent it from being spurious excluded from import *.

@bluetech
Copy link
Contributor Author

bluetech commented Jun 29, 2019

@msullivan Yes, testing with *-imports I see some unexpected things.

If I understand module_public and module_hidden correctly, this is what they mean (with --no-implicit-reexport enabled):

module_public module_hidden meaning
false false can be imported directly, not included in import *
false true cannot be imported directly, not included in import *
true false can be imported directly, included in import *
true true cannot be imported directly, included in import *

But the "cannot be imported directly, included in import *" case, I think makes no sense? That is, I think that if a symbol is imported with import *, it should always be possible to import it directly.

If true, this suggests that instead of two booleans, an enum should be used, maybe something like this:

class SymbolModuleVisibility:
    """The visibility of the symbol in a module, i.e. whether and how another module can import the symbol from this module.

    Only relevant for module symbol tables.
    """
    # Another module cannot import this symbol.
    hidden = 1
    # Another module can import this symbol directly.
    exported = 2
    # Another module can import this symbol directly, and with `from <module> import *`.
    exported_public = 3

Things which affect visibility (AFAICS) are:

  • Name starts with _.
  • Name appears in __all__.
  • Name imported from another module with as (from another_module import x as y).
  • Name is a module import without as (import another_module).
  • Is stub file (per rules in PEP 484 Stub Files section).
  • Implicit reexports are enabled.

Do you think it makes sense to get into that, or just do the fix you suggested and be done with it?

@msullivan
Copy link
Collaborator

Removing the potential invalid state by using defined constants seems reasonable. (Unfortunately we can't use an Enum right now because of limitations in mypyc, a compiler we use to compile mypy)

@bluetech bluetech force-pushed the implicit-reexport-all branch from 1defc19 to 6022dac Compare June 29, 2019 22:43
@bluetech
Copy link
Contributor Author

I applied @msullivan's suggestion and also adjusted the docs.

I also added a second commit which switches the code to use an enum as proposed in the comment above.

I want to add more test cases and probably adjust the terminology in the second commit, so marking as WIP.

@bluetech bluetech changed the title Fix variables in __all__ being flagged by --no-implicit-reexport WIP: Fix variables in __all__ being flagged by --no-implicit-reexport Jun 29, 2019
mypy/nodes.py Outdated
Only relevant for module symbol tables.
"""
# Another module cannot import this symbol.
hidden = 1 # type: Final
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these names all caps

@bluetech
Copy link
Contributor Author

bluetech commented Jul 7, 2019

Left a comment in the issue #7042 (comment) on what's blocking this.

@ilevkivskyi
Copy link
Member

@bluetech
What is the status of this PR? It fixes a high-priority issue and looks mostly ready apart from a merge conflict caused by moving semantic analyzer files.

@bluetech
Copy link
Contributor Author

@ilevkivskyi Sorry for not following up. This needs some work/research, in particular spec'ing out the rules for each of these:

  • runtime imports
  • type checking regular files with implicit exports
  • type checking regular files without implicit exports
  • type checking stub files

per each type of import and export (partial list?)

  • import x
  • import x as z
  • import x.y (what happens to x)
  • import x.y as z
  • from x import y
  • from x import y as z
  • from x import *
  • __all__ defined?
  • symbol starts with _?

and I haven't had enough patience to do it. For now I'd like to close it, but instead I'll just try to go back to the initial commit and rebase which at least makes the __all__ exports explicit, and you can decide if you want to merge it or close it.

The natural expectation is that when a variable is explicitly added to a
module's `__all__`, the module explicitly means to export it.

Previously, this was not the case, and a `from [..] import [..] as [..]`
reexport was required by `--no-implicit-export` even if the variable is
listed in `__all__`.

Fixes python#7042
@bluetech bluetech force-pushed the implicit-reexport-all branch from 6022dac to 133ba62 Compare August 17, 2019 12:13
@bluetech bluetech changed the title WIP: Fix variables in __all__ being flagged by --no-implicit-reexport Fix variables in __all__ being flagged by --no-implicit-reexport Aug 17, 2019
@bluetech
Copy link
Contributor Author

Updated as described above.

@ilevkivskyi
Copy link
Member

I think we can keep this open (after #7361 is merged) as a place to discuss possible follow-up refactoring/clean-up that will replace module_public/module_hidden with an enum and clarifies the corner cases.

ilevkivskyi pushed a commit that referenced this pull request Aug 19, 2019
…abled (#7361)

Based on #7099.

Currently, in regular files, symbols imported into module A with `from B
import *` are considered explicitly exported from module A as well, i.e.
they are considered part of module's A public API.

This behavior makes sense in stub files as a shorthand (and is specified
in PEP 484), but for regular files I think it is better to be explicit:
add the symbols to `__all__`.

Fixes #7042
Further discussion: #7042 (comment)
@bluetech
Copy link
Contributor Author

This was squashed in to e9cf4f2 (PR #7361).

@bluetech bluetech closed this Aug 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

--no-implicit-reexport should treat names in __all__ as exported
3 participants