Skip to content
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

Support conditional import in functions/classes #2304

Merged
merged 5 commits into from Oct 28, 2016
Merged

Support conditional import in functions/classes #2304

merged 5 commits into from Oct 28, 2016

Conversation

drvink
Copy link
Contributor

@drvink drvink commented Oct 23, 2016

Fixes #2260.

@codecov-io
Copy link

codecov-io commented Oct 23, 2016

Current coverage is 83.10% (diff: 93.75%)

Merging #2304 into master will increase coverage by 0.01%

@@             master      #2304   diff @@
==========================================
  Files            72         72          
  Lines         19013      19052    +39   
  Methods           0          0          
  Messages          0          0          
  Branches       3909       3926    +17   
==========================================
+ Hits          15799      15834    +35   
- Misses         2616       2617     +1   
- Partials        598        601     +3   

Powered by Codecov. Last update 2e19181...c2ca2db

@gvanrossum
Copy link
Member

Here's a test:

diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test
index d540a2d..bf3c9f7 100644
--- a/test-data/unit/check-unreachable-code.test
+++ b/test-data/unit/check-unreachable-code.test
@@ -372,7 +372,7 @@ def foo() -> None:
 [out]
 main: note: In function "foo":

-[case testSysVersionInfoInMethod]
+[case testSysPlatformInMethod]
 import sys
 class C:
     def foo(self) -> None:
@@ -385,6 +385,19 @@ class C:
 [out]
 main: note: In member "foo" of class "C":

+[case testSysPlatformInFunctionImport]
+import sys
+def foo() -> None:
+    if sys.platform != 'fictional':
+        import a
+    else:
+        import b as a
+    a.x
+[file a.py]
+x = 1
+[builtins fixtures/ops.pyi]
+[out]    
+
 [case testCustomSysVersionInfo]
 # flags: --python-version 3.2
 import sys

You have to apply it yourself, I don't have push permission to your fork.

@drvink
Copy link
Contributor Author

drvink commented Oct 24, 2016

Sorry, I just saw this now (and weird, you should have permission). I added the test.

@gvanrossum
Copy link
Member

Thanks!

(Suggestion: It's actually easier for us to review if you don't squash your local commits. We squash them for you when we merge the PR.)

Copy link
Member

@gvanrossum gvanrossum 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 well-crafted. It also looks remarkably complex (though I suspect every bit is needed!). On the off-chance that there's a better way I'm deferring to @JukkaL. But likely it's okay with him too -- and you've probably fixed some minor subtle thing in the process!


def visit_func_def(self, func: FuncDef) -> None:
sem = self.sem
func.is_conditional = sem.block_depth[-1] > 0
func._fullname = sem.qualified_name(func.name())
if func.name() in sem.globals:
at_module = sem.is_module_scope()
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's worth a separate local variable to shorten the condition on the next line (and notice our line length limit is 99).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, oops. The separation was indeed to make the line shorter (which mattered in some WIP copy) but is now unnecessary. I'll remove it.

Copy link
Contributor Author

@drvink drvink Oct 24, 2016

Choose a reason for hiding this comment

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

(In the simplified version, at_module still eliminates some redundancy, so it has returned.)

@@ -2418,22 +2418,25 @@ def visit_await_expr(self, expr: AwaitExpr) -> None:
# Helpers
#

def lookup(self, name: str, ctx: Context) -> SymbolTableNode:
def lookup(self, name: str, ctx: Context, include_globals: bool = True,
report: bool = True) -> SymbolTableNode:
"""Look up an unqualified name in all active namespaces."""
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice to explain in the docstring what the new Boolean arguments mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will fix.

@@ -2418,22 +2418,25 @@ def visit_await_expr(self, expr: AwaitExpr) -> None:
# Helpers
#

def lookup(self, name: str, ctx: Context) -> SymbolTableNode:
def lookup(self, name: str, ctx: Context, include_globals: bool = True,
report: bool = True) -> SymbolTableNode:
Copy link
Member

Choose a reason for hiding this comment

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

The name "report(to me, in the context or mypy) suggests a linkage to the report generation functionality (e.g.--linecount-report,--html-report). Maybe lengthen toreport_errors`?

Alternatively, maybe combine the two flags? They are always both on or both off. (Also, when off, lookup() is used as a Boolean, but I'm not sure how to benefit from that to make the code simpler.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

report_errors will be less ambiguous, so I'll change it.

I intentionally made include_globals separate since even though they're only currently used both on or off, someone might later want to use the function in a way where not searching for globals is desired, but reporting an error is. Also, what to search and whether to report an error are totally unrelated features, so this makes the function's behavior clear, precise, and intuitive to its arguments, rather than subtly behaving differently based on a single toggle.

Personally, I'd split lookup into different functions for each step, and then have a function where the caller could specify which steps they wanted and whether they wanted an error reported, but that would be an unrelated refactor, so I opted for the least invasive change.

@drvink
Copy link
Contributor Author

drvink commented Oct 24, 2016

It seems it might not need to be as complicated: all tests still pass and the functionality still works if I remove all the code for adding local symbols. I'm not familiar enough with mypy's internals to understand why (it was hard to determine how much needed to be added during the first pass to make this work), so I'm pushing a simplified version on top of the cleanups to the original, and @JukkaL can just ignore the simplified one (the most recent commit) if it's wrong.

@gvanrossum
Copy link
Member

gvanrossum commented Oct 25, 2016 via email

sem.globals[func.name()] = SymbolTableNode(GDEF, func, sem.cur_mod_id)
if at_module:
sem.globals[func.name()] = SymbolTableNode(GDEF, func, sem.cur_mod_id)
sem.function_stack.append(func)
Copy link
Member

Choose a reason for hiding this comment

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

So this is the key part of this diff (processing function bodies in the FirstPass). Could you add a comment drawing attention to it? Also, I wonder if it would be prudent to add matching calls to self.errors.push_function() and self.errors.pop_function()?

@gvanrossum
Copy link
Member

I'm going to make that edit (adding a comment) myself and then merge.

@drvink
Copy link
Contributor Author

drvink commented Oct 28, 2016

Sorry for being slow to reply. That should be fine, though I think you meant func.name() :)

@gvanrossum
Copy link
Member

(Sorry bout that! Juggling too many eggs today. :-)

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.

None yet

3 participants