-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Handle python 3.6 f-strings without error #2622
Conversation
mypy/fastparse.py
Outdated
@@ -769,6 +769,11 @@ def visit_Str(self, n: ast35.Str) -> Union[UnicodeExpr, StrExpr]: | |||
else: | |||
return UnicodeExpr(n.s) | |||
|
|||
# JoinedStr(expr* values) |
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 whole method should be inside something like if hasattr(ast35, 'JoinedStr'):
so that mypy will still work with the previous version of typed_ast.
|
||
|
||
[case testFStringParseOk] | ||
a = f'foobar' |
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.
You need # flags: --fast-parser
here (see how other tests use it) else you get a SyntaxError.
Can you also add tests showing that the expressions in {}
are actually type-checked?
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.
PS. To quickly run just this test, try: pytest -n0 -k testFStringParseOk
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.
Thanks for the tip, better than python runtests.py testcheck
that I was using.
I've just added another test to check type checking of expressions but I can't get it working.
If I put the content in a test_f_string.py
file and run mypy --fast-parser --python-version 3.6 --show-traceback test_f_strings.py
it runs without any error.
However running the test with pytest -n0 -k testFStringParseOk
gives me:
___________________________________________________ testFStringTypecheckExpression ____________________________________________________
data: /Users/achauve/dev/github.com/achauve/mypy/test-data/unit/check-expressions.test:1169:
../../mypy/test/testcheck.py:113: in run_case
self.run_case_once(testcase)
../../mypy/test/testcheck.py:190: in run_case_once
assert_string_arrays_equal(output, a, msg.format(testcase.file, testcase.line))
../../mypy/test/helpers.py:85: in assert_string_arrays_equal
raise AssertionFailure(msg)
E mypy.myunit.AssertionFailure: Invalid type checker output (/Users/achauve/dev/github.com/achauve/mypy/test-data/unit/check-expressions.test, line 1169)
-------------------------------------------------------- Captured stderr call ---------------------------------------------------------
Expected:
Actual:
main:3: error: Variable annotation syntax is only suppoted in Python 3.6, use type comment instead (diff)
It doesn't use python 3.6 syntax. How can I specify it in the tests?
|
||
[case testFStringTypecheckExpression] | ||
# flags: --fast-parser | ||
# flags: --python-version 3.6 |
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.
You should combine the flags like so:
# flags: --fast-parser --python-version 3.6
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.
Thanks
a = 'foo' | ||
b: str | ||
b = f'{a}bar' | ||
b == 'foobar' |
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 get Unsupported left operand type for == ("str")
here, which I think is a red herring. Did you mean =
instead of ==
?
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.
No I meant ==
.
I think I'm missing something here. Isn't it supposed to pass?
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, hm. It's supposed to pass in real life, but in the tests we're using a set of minimal stubs (see lib-stub/* and fixtures/*, and the [builtins ...]
directove). I would just use a different test that doesn't use ==
-- it's not like the tests run this code, it's only type-checked.
a: str | ||
a = 'foo' | ||
b: str | ||
b = f'{a}bar' |
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'd also try something with a type error inside the {}
, e.g.
a = 1
b = ''
f'{a + b}'
Note that plain a + b
gives an error, so f'{a + b}'
should also gives an error.
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.
Yes absolutely I was in the process of adding such a test, it's working minus the variation in error output (diff)
abdc281
to
2112398
Compare
f5714c6
to
025c948
Compare
a = 'foo' | ||
b: str | ||
b = f'{a}bar' | ||
b == 'foobar' |
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, hm. It's supposed to pass in real life, but in the tests we're using a set of minimal stubs (see lib-stub/* and fixtures/*, and the [builtins ...]
directove). I would just use a different test that doesn't use ==
-- it's not like the tests run this code, it's only type-checked.
f'{1 + "a"}' | ||
f'{1 + 1}' | ||
[out] | ||
main:7: error: Unsupported operand types for + ("int" and "str") (diff) |
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.
You don't need the (diff)
bit at the end.
I've cleanup the tests and removed the |
-> should we use a more complex solution by building an ast expression of `str(...)` to get rid of the casts?
mypy/fastparse.py
Outdated
@@ -776,7 +776,8 @@ def visit_Str(self, n: ast35.Str) -> Union[UnicodeExpr, StrExpr]: | |||
def visit_JoinedStr(self, n: ast35.JoinedStr) -> StrExpr: | |||
result_string_expression = StrExpr('') | |||
for value in n.values: | |||
result_string_expression = OpExpr('+', result_string_expression, self.visit(value)) | |||
value_as_string_expr = cast(StrExpr, self.visit(value)) | |||
result_string_expression = cast(StrExpr, OpExpr('+', result_string_expression, value_as_string_expr)) |
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 wonder if you could use asserts instead of casts, e.g.
value_as_string_expr = self.visit(value)
assert isinstance(value_as_string_expr, StrExpr)
r = OpExpr('+', result_string_expression, value_as_string_expr)
assert isinstance(r, StrExpr)
result_string_expression = r
That way if the type is ever not what you expect it'll fail right there rather than mysteriously failing at some later point.
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 used cast because otherwise I thought it would systematically fail without a call to str(...)
. Am I missing something?
I thought another typesafe solution would be to replace casts by something like (untested) CallExpr(NameExpr('str'), OpExpr('+', result_string_expression, value_as_string_expr))
? but it's rather complex compared to untyped casts, and at the end every expression in a f-string is indeed passed to a str()
call?
mypy/fastparse.py
Outdated
if hasattr(ast35, 'JoinedStr'): | ||
# JoinedStr(expr* values) | ||
@with_line | ||
def visit_JoinedStr(self, n: ast35.JoinedStr) -> StrExpr: |
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.
You're also going to have to add JoinedStr
and FormattedValue
to the stubs for typed_ast.ast35
in typeshed.
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.
Yes thanks for the tip
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.
Done python/typeshed#803
Hm, my solution doesn't work either (but the traceback it gives shows that your |
Thanks for the review. I'm not sure what you mean by "your |
There seems to be confusion here between the types of the expressions being analyzed and the type of the mypy variables used to do the type checking. The failing assert (when running mypy) proves that the cast (which is happening in mypy) is wrong because the type of the mypy expression being cast is not what the cast says it should be. I tried something like this to prove it: a = ''
b = f'.{a}.' and the second assert in my version failed because You can probably fix this by using The other thing is that you have to somehow insert a node around each expression inside We would then effectively get a translation from e.g. b = f"foo {a + 1} bar" to b = "foo " + (a + 1).__str__() + " bar" A remaining issue to solve is what to do with the optional formatting after the value, e.g. (from PEP 498): f'my anniversary is {anniversary:%A, %B %d, %Y}.' But that can be done in a separate PR -- it's similar in scope and complexity as making sure that Finally: the most correct method call to insert would actually be |
Indeed there was some confusion on my part. I'll try to be more specific and answer your different points. 1 - Casts vs asserts vs loosing up the return type of method 2 - Inserting a call to 3 - Handling (ie typechecking) optional formatting params of expressions inside f-strings. |
No, you need the call even without (3). Consider this: |
Exact, I'll add the missing test case. The version of the code using the casts was working though ;-). I've tried to use:
and it's working fine when I run mypy in the command line on a test file (using the same test cases as in the unit tests). But when I run the unit test with
and I don't know why. It seems like during |
ba097e0
to
4847e27
Compare
So to give another hint, if you add
Note that there's no line number! You have to pass the line number along to your calls constructing new nodes (there are some other examples for that in the same file). |
@kirbyfan64 Thanks for the tip, I'll use my forks in the meantime ;-) |
@gvanrossum any news on typed_ast and when this PR could be merged? Thx! |
@ddfisher can you fill us in? |
Could you please explain what BLOCKED means. |
As always blocked means delayed. See also description of PR:
|
This should be unblocked now! |
@ddfisher Thanks! :) |
@achauve I don't have a preference between merging or rebasing from master; we squash commits when we merge into master anyways. Sometimes for an old PR, merging is easier than repeatedly solving the rebase conflicts. Once your tests passing I'll review again! |
The tests are failing on python < 3.6 and I don't know why for now (f-string tests pass when using |
@achauve I made a PR python/typed_ast#32 to fix this. |
@ilevkivskyi ok thanks! |
Merged the PR -- releasing typed-ast 1.0.1 now. |
typed-ast 1.0.1 released. Thanks for the fix @ilevkivskyi! |
@ddfisher Thank you for fast reaction! I have tried this branch and now all tests pass on 3.4 and 3.5.1 @achauve I have found that in your PR
The error looks strange to me, f-string literals cannot produce anything but |
@ilevkivskyi Thanks for the fix! For your test case, is |
Yes, exactly. |
As mypy typechecks the expression inside the f-string, isn't it sound that there is an error? |
@gvanrossum Thanks to @ilevkivskyi the PR is now all green! |
If you give it a type, |
ok |
Linked to #2265