Skip to content

Commit 6a536d0

Browse files
committed
Implement IndentationError
1 parent e2bd541 commit 6a536d0

File tree

6 files changed

+78
-15
lines changed

6 files changed

+78
-15
lines changed

compiler/src/error.rs

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use rustpython_parser::error::{LexicalErrorType, ParseError, ParseErrorType};
22
use rustpython_parser::location::Location;
3+
use rustpython_parser::token::Tok;
34

45
use std::error::Error;
56
use std::fmt;
@@ -41,6 +42,24 @@ pub enum CompileErrorType {
4142
}
4243

4344
impl CompileError {
45+
pub fn is_indentation_error(&self) -> bool {
46+
if let CompileErrorType::Parse(parse) = &self.error {
47+
if let ParseErrorType::Lexical(lex) = parse {
48+
if let LexicalErrorType::IndentationError = lex {
49+
return true;
50+
}
51+
} else if let ParseErrorType::UnrecognizedToken(token, expected) = parse {
52+
if *token == Tok::Indent {
53+
return true;
54+
}
55+
if expected.clone() == Some("Indent".to_string()) {
56+
return true;
57+
}
58+
}
59+
}
60+
false
61+
}
62+
4463
pub fn is_tab_error(&self) -> bool {
4564
if let CompileErrorType::Parse(parse) = &self.error {
4665
if let ParseErrorType::Lexical(lex) = parse {

parser/src/error.rs

+26-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub enum LexicalErrorType {
2020
StringError,
2121
UnicodeError,
2222
NestingError,
23+
IndentationError,
2324
TabError,
2425
DefaultArgumentError,
2526
PositionalArgumentError,
@@ -36,6 +37,9 @@ impl fmt::Display for LexicalErrorType {
3637
LexicalErrorType::FStringError(error) => write!(f, "Got error in f-string: {}", error),
3738
LexicalErrorType::UnicodeError => write!(f, "Got unexpected unicode"),
3839
LexicalErrorType::NestingError => write!(f, "Got unexpected nesting"),
40+
LexicalErrorType::IndentationError => {
41+
write!(f, "unindent does not match any outer indentation level")
42+
}
3943
LexicalErrorType::TabError => {
4044
write!(f, "inconsistent use of tabs and spaces in indentation")
4145
}
@@ -121,7 +125,7 @@ pub enum ParseErrorType {
121125
/// Parser encountered an invalid token
122126
InvalidToken,
123127
/// Parser encountered an unexpected token
124-
UnrecognizedToken(Tok, Vec<String>),
128+
UnrecognizedToken(Tok, Option<String>),
125129
/// Maps to `User` type from `lalrpop-util`
126130
Lexical(LexicalErrorType),
127131
}
@@ -143,10 +147,19 @@ impl From<LalrpopError<Location, Tok, LexicalError>> for ParseError {
143147
error: ParseErrorType::Lexical(error.error),
144148
location: error.location,
145149
},
146-
LalrpopError::UnrecognizedToken { token, expected } => ParseError {
147-
error: ParseErrorType::UnrecognizedToken(token.1, expected),
148-
location: token.0,
149-
},
150+
LalrpopError::UnrecognizedToken { token, expected } => {
151+
// Hacky, but it's how CPython does it. See PyParser_AddToken,
152+
// in particular "Only one possible expected token" comment.
153+
let expected = if expected.len() == 1 {
154+
Some(expected[0].clone())
155+
} else {
156+
None
157+
};
158+
ParseError {
159+
error: ParseErrorType::UnrecognizedToken(token.1, expected),
160+
location: token.0,
161+
}
162+
}
150163
LalrpopError::UnrecognizedEOF { location, .. } => ParseError {
151164
error: ParseErrorType::EOF,
152165
location,
@@ -167,8 +180,14 @@ impl fmt::Display for ParseErrorType {
167180
ParseErrorType::EOF => write!(f, "Got unexpected EOF"),
168181
ParseErrorType::ExtraToken(ref tok) => write!(f, "Got extraneous token: {:?}", tok),
169182
ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
170-
ParseErrorType::UnrecognizedToken(ref tok, _) => {
171-
write!(f, "Got unexpected token {}", tok)
183+
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
184+
if *tok == Tok::Indent {
185+
write!(f, "unexpected indent")
186+
} else if expected.clone() == Some("Indent".to_string()) {
187+
write!(f, "expected an indented block")
188+
} else {
189+
write!(f, "Got unexpected token {}", tok)
190+
}
172191
}
173192
ParseErrorType::Lexical(ref error) => write!(f, "{}", error),
174193
}

parser/src/lexer.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -784,11 +784,8 @@ where
784784
break;
785785
}
786786
Ordering::Greater => {
787-
// TODO: handle wrong indentations
788787
return Err(LexicalError {
789-
error: LexicalErrorType::OtherError(
790-
"Non matching indentation levels!".to_string(),
791-
),
788+
error: LexicalErrorType::IndentationError,
792789
location: self.get_pos(),
793790
});
794791
}

parser/src/python.lalrpop

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ FileLine: ast::Suite = {
3939

4040
Suite: ast::Suite = {
4141
SimpleStatement,
42-
"\n" indent <s:Statement+> dedent => s.into_iter().flatten().collect(),
42+
"\n" Indent <s:Statement+> Dedent => s.into_iter().flatten().collect(),
4343
};
4444

4545
Statement: ast::Suite = {
@@ -1124,8 +1124,8 @@ extern {
11241124
type Error = LexicalError;
11251125

11261126
enum lexer::Tok {
1127-
indent => lexer::Tok::Indent,
1128-
dedent => lexer::Tok::Dedent,
1127+
Indent => lexer::Tok::Indent,
1128+
Dedent => lexer::Tok::Dedent,
11291129
StartProgram => lexer::Tok::StartProgram,
11301130
StartStatement => lexer::Tok::StartStatement,
11311131
StartExpression => lexer::Tok::StartExpression,

tests/snippets/invalid_syntax.py

+26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@ def valid_func():
1414
else:
1515
raise AssertionError("Must throw syntax error")
1616

17+
src = """
18+
if True:
19+
pass
20+
"""
21+
22+
with assert_raises(IndentationError):
23+
compile(src, '', 'exec')
24+
25+
src = """
26+
if True:
27+
pass
28+
pass
29+
"""
30+
31+
with assert_raises(IndentationError):
32+
compile(src, '', 'exec')
33+
34+
src = """
35+
if True:
36+
pass
37+
pass
38+
"""
39+
40+
with assert_raises(IndentationError):
41+
compile(src, '', 'exec')
42+
1743
src = """
1844
if True:
1945
pass

vm/src/vm.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,9 @@ impl VirtualMachine {
416416

417417
#[cfg(feature = "rustpython-compiler")]
418418
pub fn new_syntax_error(&self, error: &CompileError) -> PyObjectRef {
419-
let syntax_error_type = if error.is_tab_error() {
419+
let syntax_error_type = if error.is_indentation_error() {
420+
self.ctx.exceptions.indentation_error.clone()
421+
} else if error.is_tab_error() {
420422
self.ctx.exceptions.tab_error.clone()
421423
} else {
422424
self.ctx.exceptions.syntax_error.clone()

0 commit comments

Comments
 (0)