Skip to content

Commit caae28f

Browse files
committed
fix(parser): preserve tab indentation across continuation lines
1 parent b22a8c9 commit caae28f

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

‎Lib/test/test_syntax.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3273,6 +3273,30 @@ def fib(n):
32733273
except SyntaxError:
32743274
self.fail("Indented statement over multiple lines is valid")
32753275

3276+
@support.subTests("indent", ("\t", " \t", "\t ", " "))
3277+
@support.subTests("continuations", (1, 3))
3278+
@support.subTests("newline", ("\n", "\r\n"))
3279+
@support.subTests("first_statement", (False, True))
3280+
def test_continuation_tab_indentation(self, indent, continuations,
3281+
newline, first_statement):
3282+
lines = ["if True:"]
3283+
if not first_statement:
3284+
lines.append(f"{indent}pass")
3285+
lines.extend([f"{indent}\\"] * continuations)
3286+
lines.extend(["pass", f"{indent}pass", "pass", ""])
3287+
compile(newline.join(lines), "<test>", "exec")
3288+
3289+
@support.subTests("indents", (
3290+
(" ", "\t"), ("\t", " "),
3291+
))
3292+
@support.subTests("continuations", (1, 3))
3293+
def test_continuation_inconsistent_tabs(self, indents, continuations):
3294+
previous, continued = indents
3295+
lines = ["if True:", f"{previous}pass"]
3296+
lines.extend([f"{continued}\\"] * continuations)
3297+
lines.extend(["pass", ""])
3298+
self.assertRaises(TabError, compile, "\n".join(lines), "<test>", "exec")
3299+
32763300
def test_continuation_bad_indentation(self):
32773301
# Check that code that breaks indentation across multiple lines raises a syntax error
32783302

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix tab indentation checks for lines joined by explicit line continuation.
2+
Consistent tab indentation is accepted and ambiguous mixtures of tabs and
3+
spaces are rejected.

‎Parser/lexer/layout.c‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ _PyLexer_BeginLine(struct tok_state *tok)
8484
int altcol = 0;
8585
tok->layout.at_bol = 0;
8686
int cont_line_col = 0;
87+
int cont_line_altcol = 0;
8788
for (;;) {
8889
c = tok_nextc(tok);
8990
if (c == ' ') {
@@ -101,6 +102,7 @@ _PyLexer_BeginLine(struct tok_state *tok)
101102
// using backslashes. This means that if we found a backslash
102103
// preceded by whitespace, **the first one we find** determines
103104
// the level of indentation of whatever comes next.
105+
cont_line_altcol = cont_line_col ? cont_line_altcol : altcol;
104106
cont_line_col = cont_line_col ? cont_line_col : col;
105107
if ((c = _PyLexer_ContinueLine(tok)) == -1) {
106108
return -1;
@@ -136,7 +138,7 @@ _PyLexer_BeginLine(struct tok_state *tok)
136138
}
137139
if (!blankline && tok->level == 0) {
138140
col = cont_line_col ? cont_line_col : col;
139-
altcol = cont_line_col ? cont_line_col : altcol;
141+
altcol = cont_line_col ? cont_line_altcol : altcol;
140142
if (update_indentation(tok, col, altcol) < 0) {
141143
return -1;
142144
}

0 commit comments

Comments
 (0)