Skip to content

Implicit string concatenation produces different AST than CPython #4095

Closed

Description

@charliermarsh

The AST produced by RustPython differs from CPython when parsing implicitly concatenated strings.

For example, given this source:

import ast

expr = """(
    'Hello, '
    'world!'
)"""

print(ast.dump(ast.parse(expr), include_attributes=True))

CPython produces a single Constant, with a value equal to the concatenated strings:

Expr(value=Constant(value='Hello, world!', kind=None, lineno=2, col_offset=4, end_lineno=3, end_col_offset=12), lineno=1, col_offset=0, end_lineno=4, end_col_offset=1)

Whereas RustPython produces a JoinedStr consisting of two Constant sub-expressions:

Expr { value: Located { location: Location { row: 2, column: 6 }, custom: (), node: JoinedStr { values: [Located { location: Location { row: 2, column: 6 }, custom: (), node: Constant { value: Str("Hello, "), kind: None } }, Located { location: Location { row: 3, column: 6 }, custom: (), node: Constant { value: Str("world!"), kind: None } }] } } }

There are some other differences too, e.g.:

  • CPython seems to propagate the string kind of the first substring. So if the first substring is u'Hello, ', then the whole thing will be treated as Unicode. If the second substring is u'world!', CPython drops the u entirely. (I found this surprising.)
  • If any of the substrings are f-strings, CPython returns a JoinedStr, but exhibits the same constant-collapsing as in the provided example.

As far as I can tell, the rules are (roughly):

  • If every substring is a Constant, return a single Constant with the substrings concatened.
  • If any substring is an f-string, return a JoinedStr, then flatten out the values into that top-level JoinedStr, collapsing any adjacent Constant expressions, so that you're left with Constant expressions interspersed with FormattedValue expressions.
  • If the first substring has a "kind", apply that "kind" to any Constant expressions.

(I haven't been able to find this in any documentation, only from testing out CPython 3.8.12 itself.)

Happy to attempt a PR, unless this behavior is intentional.

(By the way: very impressive project!)

Activity

  1. charliermarsh commented on Aug 17, 2022

    @charliermarsh
    CollaboratorAuthor

    I think this can be fixed by modifying the <location:@L> <s:(@L string)+> =>? case in the LALRPOP file.

  2. charliermarsh commented on Aug 18, 2022

    @charliermarsh
    CollaboratorAuthor

    Made an attempt with #4097.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-compilerArea: compilerC-compatA discrepancy between RustPython and CPython

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions