Skip to content

Introduce SuiteStatement #5326

@MichaReiser

Description

@MichaReiser

RustPython's AST structure for the following two programs is identical (the ranges differ)

# Program 1
if True:
    pass
elif False:
    pass


# Program 2
if True:
    pass
else:
    if False:
        pass
If(
    StmtIf {
        range: 0..38,
        test: Constant(
            ExprConstant {
                range: 3..7,
                value: Bool(
                    true,
                ),
                kind: None,
            },
        ),
        body: [
            Pass(
                StmtPass {
                    range: 13..17,
                },
            ),
        ],
        orelse: [
            If(
                StmtIf {
                    range: 18..38,
                    test: Constant(
                        ExprConstant {
                            range: 23..28,
                            value: Bool(
                                false,
                            ),
                            kind: None,
                        },
                    ),
                    body: [
                        Pass(
                            StmtPass {
                                range: 34..38,
                            },
                        ),
                    ],
                    orelse: [],
                },
            ),
        ],
    },
),

This is problematic because the formatter incorrectly assumes that the nested if in the second program is an elif and collapses the nested if. This isn't something a formatter should do. This representation does make sense for an interpreter. It's actually a neat little optimisation that the parser performs.

The solution for this is to change Suite from a Vec<Statement> to its own SuiteStatement AST node and change the orelse type (and any other field that represents a body) from Suite to Option<Stmt>.

This would change the representation of the first program to:

If(
    StmtIf {
        range: 0..38,
        test: Constant(
            ExprConstant {
                range: 3..7,
                value: Bool(
                    true,
                ),
                kind: None,
            },
        ),
        body: [
            Pass(
                StmtPass {
                    range: 13..17,
                },
            ),
        ],
        orelse: Some(If(
              StmtIf {
                  range: 18..38,
                  test: Constant(
                      ExprConstant {
                          range: 23..28,
                          value: Bool(
                              false,
                          ),
                          kind: None,
                      },
                  ),
                  body: [
                      Pass(
                          StmtPass {
                              range: 34..38,
                          },
                      ),
                  ],
                  orelse: None
              },
          ),
    }),
),

and of the second program:

If(
    StmtIf {
        range: 0..38,
        test: Constant(
            ExprConstant {
                range: 3..7,
                value: Bool(
                    true,
                ),
                kind: None,
            },
        ),
        body: [
            Pass(
                StmtPass {
                    range: 13..17,
                },
            ),
        ],
        orelse: Some(SuiteStatement(SuiteStatement([
            If(
                StmtIf {
                    range: 18..38,
                    test: Constant(
                        ExprConstant {
                            range: 23..28,
                            value: Bool(
                                false,
                            ),
                            kind: None,
                        },
                    ),
                    body: [
                        Pass(
                            StmtPass {
                                range: 34..38,
                            },
                        ),
                    ],
                    orelse: None,
                },
            ),
        ])),
    },
),

which is unambiguous

This requires changes to:

  • RustPython's parser
  • Ruff
  • The visitor implementations
  • The formatter

Alternatives

  • Extract the indent from the source to distinguish the two cases. That's what we currently do when extracting comments but it is so easy to get wrong. We would need to do the same in the unparse of the linter

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions