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
Support conditional import in functions/classes #2304
Conversation
Current coverage is 83.10% (diff: 93.75%)@@ 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
|
Here's a test:
You have to apply it yourself, I don't have push permission to your fork. |
Sorry, I just saw this now (and weird, you should have permission). I added the test. |
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.) |
There was a problem hiding this 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() |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.""" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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 to
report_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.)
There was a problem hiding this comment.
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.
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. |
Please let us worry about future scenarios. :-)
|
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) |
There was a problem hiding this comment.
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()?
I'm going to make that edit (adding a comment) myself and then merge. |
Sorry for being slow to reply. That should be fine, though I think you meant |
(Sorry bout that! Juggling too many eggs today. :-) |
Fixes #2260.