Skip to content

Commit 5b2cfb4

Browse files
committed
Make from __future__ imports a syntactic construct in the compiler
1 parent 80f1146 commit 5b2cfb4

3 files changed

Lines changed: 74 additions & 5 deletions

File tree

compiler/src/compile.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct Compiler<O: OutputStream = BasicOutputStream> {
2929
source_path: Option<String>,
3030
current_source_location: ast::Location,
3131
current_qualified_path: Option<String>,
32+
done_with_future_stmts: bool,
3233
ctx: CompileContext,
3334
opts: CompileOpts,
3435
}
@@ -166,6 +167,7 @@ impl<O: OutputStream> Compiler<O> {
166167
source_path: None,
167168
current_source_location: ast::Location::default(),
168169
current_qualified_path: None,
170+
done_with_future_stmts: false,
169171
ctx: CompileContext {
170172
in_loop: false,
171173
func: FunctionContext::NoFunction,
@@ -334,6 +336,16 @@ impl<O: OutputStream> Compiler<O> {
334336
self.set_source_location(statement.location);
335337
use ast::StatementType::*;
336338

339+
match &statement.node {
340+
// we do this here because `from __future__` still executes that `from` statement at runtime,
341+
// we still need to compile the ImportFrom down below
342+
ImportFrom { module, names, .. } if module.as_deref() == Some("__future__") => {
343+
self.compile_future_features(&names)?
344+
}
345+
// if we find any other statement, stop accepting future statements
346+
_ => self.done_with_future_stmts = true,
347+
}
348+
337349
match &statement.node {
338350
Import { names } => {
339351
// import a, b, c as d
@@ -2132,6 +2144,38 @@ impl<O: OutputStream> Compiler<O> {
21322144
Ok(())
21332145
}
21342146

2147+
fn compile_future_features(
2148+
&mut self,
2149+
features: &[ast::ImportSymbol],
2150+
) -> Result<(), CompileError> {
2151+
if self.done_with_future_stmts {
2152+
return Err(CompileError {
2153+
error: CompileErrorType::InvalidFuturePlacement,
2154+
location: self.current_source_location.clone(),
2155+
source_path: self.source_path.clone(),
2156+
statement: None,
2157+
});
2158+
}
2159+
for feature in features {
2160+
match &*feature.symbol {
2161+
// Python 3 features; we've already implemented them by default
2162+
"nested_scopes" | "generators" | "division" | "absolute_import"
2163+
| "with_statement" | "print_function" | "unicode_literals" => {}
2164+
// "generator_stop" => {}
2165+
// "annotations" => {}
2166+
other => {
2167+
return Err(CompileError {
2168+
error: CompileErrorType::InvalidFutureFeature(other.to_owned()),
2169+
location: self.current_source_location.clone(),
2170+
source_path: self.source_path.clone(),
2171+
statement: None,
2172+
})
2173+
}
2174+
}
2175+
}
2176+
Ok(())
2177+
}
2178+
21352179
// Scope helpers:
21362180
fn enter_scope(&mut self) {
21372181
// println!("Enter scope {:?}", self.symbol_table_stack);

compiler/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub enum CompileErrorType {
5858
InvalidAwait,
5959
AsyncYieldFrom,
6060
AsyncReturnValue,
61+
InvalidFuturePlacement,
62+
InvalidFutureFeature(String),
6163
}
6264

6365
impl CompileError {
@@ -106,6 +108,12 @@ impl fmt::Display for CompileError {
106108
CompileErrorType::AsyncReturnValue => {
107109
"'return' with value inside async generator".to_owned()
108110
}
111+
CompileErrorType::InvalidFuturePlacement => {
112+
"from __future__ imports must occur at the beginning of the file".to_owned()
113+
}
114+
CompileErrorType::InvalidFutureFeature(feat) => {
115+
format!("future feature {} is not defined", feat)
116+
}
109117
};
110118

111119
if let Some(statement) = &self.statement {

tests/snippets/invalid_syntax.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ def valid_func():
77
yield 2
88
"""
99

10-
try:
10+
with assert_raises(SyntaxError) as ae:
1111
compile(src, 'test.py', 'exec')
12-
except SyntaxError as ex:
13-
assert ex.lineno == 5
14-
else:
15-
raise AssertionError("Must throw syntax error")
12+
assert ae.exception.lineno == 5
1613

1714
src = """
1815
if True:
@@ -60,3 +57,23 @@ def valid_func():
6057

6158
with assert_raises(SyntaxError):
6259
compile(src, 'test.py', 'exec')
60+
61+
src = """
62+
from __future__ import not_a_real_future_feature
63+
"""
64+
65+
with assert_raises(SyntaxError):
66+
compile(src, 'test.py', 'exec')
67+
68+
src = """
69+
a = 1
70+
from __future__ import print_function
71+
"""
72+
73+
with assert_raises(SyntaxError):
74+
compile(src, 'test.py', 'exec')
75+
76+
src = """
77+
from __future__ import print_function
78+
"""
79+
compile(src, 'test.py', 'exec')

0 commit comments

Comments
 (0)