Operator-precedence parsing
diff --git a/src/expr.rs b/src/expr.rs
index 1866ca8..419a8c1 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1008,8 +1008,7 @@
 #[cfg(any(feature = "parsing", feature = "printing"))]
 #[cfg(feature = "full")]
 fn arm_expr_requires_comma(expr: &Expr) -> bool {
-    // see https://github.com/rust-lang/rust/blob/eb8f2586e
-    //                       /src/libsyntax/parse/classify.rs#L17-L37
+    // see https://github.com/rust-lang/rust/blob/eb8f2586e/src/libsyntax/parse/classify.rs#L17-L37
     match *expr {
         Expr::Unsafe(..)
         | Expr::Block(..)
@@ -1063,6 +1062,41 @@
     #[derive(Copy, Clone)]
     pub struct AllowBlock(bool);
 
+    #[derive(Copy, Clone, PartialEq, PartialOrd)]
+    enum Precedence {
+        Any,
+        Assign,
+        Placement,
+        Range,
+        Or,
+        And,
+        Compare,
+        BitOr,
+        BitXor,
+        BitAnd,
+        Shift,
+        Arithmetic,
+        Term,
+        Cast,
+    }
+
+    impl Precedence {
+        fn of(op: &BinOp) -> Self {
+            match *op {
+                BinOp::Add(_) | BinOp::Sub(_) => Precedence::Arithmetic,
+                BinOp::Mul(_) | BinOp::Div(_) | BinOp::Rem(_) => Precedence::Term,
+                BinOp::And(_) => Precedence::And,
+                BinOp::Or(_) => Precedence::Or,
+                BinOp::BitXor(_) => Precedence::BitXor,
+                BinOp::BitAnd(_) => Precedence::BitAnd,
+                BinOp::BitOr(_) => Precedence::BitOr,
+                BinOp::Shl(_) | BinOp::Shr(_) => Precedence::Shift,
+                BinOp::Eq(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Ne(_) | BinOp::Ge(_) | BinOp::Gt(_) => Precedence::Compare,
+                BinOp::AddEq(_) | BinOp::SubEq(_) | BinOp::MulEq(_) | BinOp::DivEq(_) | BinOp::RemEq(_) | BinOp::BitXorEq(_) | BinOp::BitAndEq(_) | BinOp::BitOrEq(_) | BinOp::ShlEq(_) | BinOp::ShrEq(_) => Precedence::Assign,
+            }
+        }
+    }
+
     impl Parse for Expr {
         fn parse(input: ParseStream) -> Result<Self> {
             ambiguous_expr(input, AllowStruct(true), AllowBlock(true))
@@ -1074,6 +1108,132 @@
         ambiguous_expr(input, AllowStruct(false), AllowBlock(true))
     }
 
+    #[cfg(feature = "full")]
+    fn parse_expr(input: ParseStream, mut lhs: Expr, allow_struct: AllowStruct, allow_block: AllowBlock, base: Precedence) -> Result<Expr> {
+        loop {
+            if input.fork().parse::<BinOp>().ok().map_or(false, |op| Precedence::of(&op) >= base) {
+                let op: BinOp = input.parse()?;
+                let precedence = Precedence::of(&op);
+                let mut rhs = unary_expr(input, allow_struct, allow_block)?;
+                loop {
+                    let next = peek_precedence(input);
+                    if next > precedence || next == precedence && precedence == Precedence::Assign {
+                        rhs = parse_expr(input, rhs, allow_struct, allow_block, next)?;
+                    } else {
+                        break;
+                    }
+                }
+                lhs = Expr::Binary(ExprBinary {
+                    attrs: Vec::new(),
+                    left: Box::new(lhs),
+                    op: op,
+                    right: Box::new(rhs),
+                });
+            } else if Precedence::Assign >= base && input.peek(Token![=]) && !input.peek(Token![==]) && !input.peek(Token![=>]) {
+                let eq_token: Token![=] = input.parse()?;
+                let mut rhs = unary_expr(input, allow_struct, allow_block)?;
+                loop {
+                    let next = peek_precedence(input);
+                    if next >= Precedence::Assign {
+                        rhs = parse_expr(input, rhs, allow_struct, allow_block, next)?;
+                    } else {
+                        break;
+                    }
+                }
+                lhs = Expr::Assign(ExprAssign {
+                    attrs: Vec::new(),
+                    left: Box::new(lhs),
+                    eq_token: eq_token,
+                    right: Box::new(rhs),
+                });
+            } else if Precedence::Placement >= base && input.peek(Token![<-]) {
+                let arrow_token: Token![<-] = input.parse()?;
+                let mut rhs = unary_expr(input, allow_struct, allow_block)?;
+                loop {
+                    let next = peek_precedence(input);
+                    if next > Precedence::Placement {
+                        rhs = parse_expr(input, rhs, allow_struct, allow_block, next)?;
+                    } else {
+                        break;
+                    }
+                }
+                lhs = Expr::InPlace(ExprInPlace {
+                    attrs: Vec::new(),
+                    place: Box::new(lhs),
+                    arrow_token: arrow_token,
+                    value: Box::new(rhs),
+                });
+            } else if Precedence::Range >= base && input.peek(Token![..]) {
+                let limits: RangeLimits = input.parse()?;
+                let rhs = if input.is_empty()
+                    || input.peek(Token![,])
+                    || input.peek(Token![;])
+                    || !allow_struct.0 && input.peek(token::Brace)
+                {
+                    None
+                } else {
+                    // We don't want to allow blocks in the rhs if we don't
+                    // allow structs.
+                    let allow_block = AllowBlock(allow_struct.0);
+                    let mut rhs = unary_expr(input, allow_struct, allow_block)?;
+                    loop {
+                        let next = peek_precedence(input);
+                        if next > Precedence::Range {
+                            rhs = parse_expr(input, rhs, allow_struct, allow_block, next)?;
+                        } else {
+                            break;
+                        }
+                    }
+                    Some(rhs)
+                };
+                lhs = Expr::Range(ExprRange {
+                    attrs: Vec::new(),
+                    from: Some(Box::new(lhs)),
+                    limits: limits,
+                    to: rhs.map(Box::new),
+                });
+            } else if Precedence::Cast >= base && input.peek(Token![as]) {
+                let as_token: Token![as] = input.parse()?;
+                let ty = input.call(Type::without_plus)?;
+                lhs = Expr::Cast(ExprCast {
+                    attrs: Vec::new(),
+                    expr: Box::new(lhs),
+                    as_token: as_token,
+                    ty: Box::new(ty),
+                });
+            } else if Precedence::Cast >= base && input.peek(Token![:]) && !input.peek(Token![::]) {
+                let colon_token: Token![:] = input.parse()?;
+                let ty = input.call(Type::without_plus)?;
+                lhs = Expr::Type(ExprType {
+                    attrs: Vec::new(),
+                    expr: Box::new(lhs),
+                    colon_token: colon_token,
+                    ty: Box::new(ty),
+                });
+            } else {
+                break;
+            }
+        }
+        Ok(lhs)
+    }
+
+    #[cfg(feature = "full")]
+    fn peek_precedence(input: ParseStream) -> Precedence {
+        if let Ok(op) = input.fork().parse() {
+            Precedence::of(&op)
+        } else if input.peek(Token![=]) && !input.peek(Token![==]) && !input.peek(Token![=>]) {
+            Precedence::Assign
+        } else if input.peek(Token![<-]) {
+            Precedence::Placement
+        } else if input.peek(Token![..]) {
+            Precedence::Range
+        } else if input.peek(Token![as]) || input.peek(Token![:]) && !input.peek(Token![::]) {
+            Precedence::Cast
+        } else {
+            Precedence::Any
+        }
+    }
+
     // Parse an arbitrary expression.
     #[cfg(feature = "full")]
     fn ambiguous_expr(
@@ -1081,7 +1241,9 @@
         allow_struct: AllowStruct,
         allow_block: AllowBlock,
     ) -> Result<Expr> {
-        assign_expr(input, allow_struct, allow_block)
+        //assign_expr(input, allow_struct, allow_block)
+        let lhs = unary_expr(input, allow_struct, allow_block)?;
+        parse_expr(input, lhs, allow_struct, allow_block, Precedence::Any)
     }
 
     #[cfg(not(feature = "full"))]
@@ -1095,313 +1257,6 @@
         or_expr(input, allow_struct, allow_block)
     }
 
-    // Parse a left-associative binary operator.
-    macro_rules! binop {
-        ($name:ident, $next:ident, |$var:ident| $parse_op:expr) => {
-            fn $name(
-                input: ParseStream,
-                allow_struct: AllowStruct,
-                allow_block: AllowBlock,
-            ) -> Result<Expr> {
-                let $var = input;
-                let mut e: Expr = $next(input, allow_struct, allow_block)?;
-                while let Some(op) = $parse_op {
-                    e = Expr::Binary(ExprBinary {
-                        attrs: Vec::new(),
-                        left: Box::new(e),
-                        op: op,
-                        right: Box::new($next(input, allow_struct, AllowBlock(true))?),
-                    });
-                }
-                Ok(e)
-            }
-        };
-    }
-
-    // <placement> = <placement> ..
-    // <placement> += <placement> ..
-    // <placement> -= <placement> ..
-    // <placement> *= <placement> ..
-    // <placement> /= <placement> ..
-    // <placement> %= <placement> ..
-    // <placement> ^= <placement> ..
-    // <placement> &= <placement> ..
-    // <placement> |= <placement> ..
-    // <placement> <<= <placement> ..
-    // <placement> >>= <placement> ..
-    //
-    // NOTE: This operator is right-associative.
-    #[cfg(feature = "full")]
-    fn assign_expr(
-        input: ParseStream,
-        allow_struct: AllowStruct,
-        allow_block: AllowBlock,
-    ) -> Result<Expr> {
-        let mut e = placement_expr(input, allow_struct, allow_block)?;
-        if input.peek(Token![=]) && !input.peek(Token![==]) && !input.peek(Token![=>]) {
-            e = Expr::Assign(ExprAssign {
-                attrs: Vec::new(),
-                left: Box::new(e),
-                eq_token: input.parse()?,
-                // Recurse into self to parse right-associative operator.
-                right: Box::new(assign_expr(input, allow_struct, AllowBlock(true))?),
-            });
-        } else if input.peek(Token![+=])
-            || input.peek(Token![-=])
-            || input.peek(Token![*=])
-            || input.peek(Token![/=])
-            || input.peek(Token![%=])
-            || input.peek(Token![^=])
-            || input.peek(Token![&=])
-            || input.peek(Token![|=])
-            || input.peek(Token![<<=])
-            || input.peek(Token![>>=])
-        {
-            e = Expr::AssignOp(ExprAssignOp {
-                attrs: Vec::new(),
-                left: Box::new(e),
-                op: BinOp::parse_assign_op(input)?,
-                // Recurse into self to parse right-associative operator.
-                right: Box::new(assign_expr(input, allow_struct, AllowBlock(true))?),
-            });
-        }
-        Ok(e)
-    }
-
-    // <range> <- <range> ..
-    //
-    // NOTE: The `in place { expr }` version of this syntax is parsed in
-    // `atom_expr`, not here.
-    //
-    // NOTE: This operator is right-associative.
-    #[cfg(feature = "full")]
-    fn placement_expr(
-        input: ParseStream,
-        allow_struct: AllowStruct,
-        allow_block: AllowBlock,
-    ) -> Result<Expr> {
-        let mut e = range_expr(input, allow_struct, allow_block)?;
-        if input.peek(Token![<-]) {
-            e = Expr::InPlace(ExprInPlace {
-                attrs: Vec::new(),
-                place: Box::new(e),
-                arrow_token: input.parse()?,
-                value: Box::new(placement_expr(input, allow_struct, AllowBlock(true))?),
-            });
-        }
-        Ok(e)
-    }
-
-    // <or> ... <or> ..
-    // <or> .. <or> ..
-    // <or> ..
-    //
-    // NOTE: This is currently parsed oddly - I'm not sure of what the exact
-    // rules are for parsing these expressions are, but this is not correct.
-    // For example, `a .. b .. c` is not a legal expression. It should not
-    // be parsed as either `(a .. b) .. c` or `a .. (b .. c)` apparently.
-    //
-    // NOTE: The form of ranges which don't include a preceding expression are
-    // parsed by `atom_expr`, rather than by this function.
-    #[cfg(feature = "full")]
-    fn range_expr(
-        input: ParseStream,
-        allow_struct: AllowStruct,
-        allow_block: AllowBlock,
-    ) -> Result<Expr> {
-        let mut e = or_expr(input, allow_struct, allow_block)?;
-        while input.peek(Token![..]) {
-            e = Expr::Range(ExprRange {
-                attrs: Vec::new(),
-                from: Some(Box::new(e)),
-                limits: input.parse()?,
-                to: {
-                    if input.is_empty()
-                        || input.peek(Token![,])
-                        || input.peek(Token![;])
-                        || !allow_struct.0 && input.peek(token::Brace)
-                    {
-                        None
-                    } else {
-                        // We don't want to allow blocks here if we don't allow
-                        // structs.
-                        Some(Box::new(or_expr(
-                            input,
-                            allow_struct,
-                            AllowBlock(allow_struct.0),
-                        )?))
-                    }
-                },
-            });
-        }
-        Ok(e)
-    }
-
-    // <and> || <and> ...
-    binop!(or_expr, and_expr, |input| if input.peek(Token![||]) {
-        Some(BinOp::Or(input.parse()?))
-    } else {
-        None
-    });
-
-    // <compare> && <compare> ...
-    binop!(and_expr, compare_expr, |input| if input.peek(Token![&&]) {
-        Some(BinOp::And(input.parse()?))
-    } else {
-        None
-    });
-
-    // <bitor> == <bitor> ...
-    // <bitor> != <bitor> ...
-    // <bitor> >= <bitor> ...
-    // <bitor> <= <bitor> ...
-    // <bitor> > <bitor> ...
-    // <bitor> < <bitor> ...
-    //
-    // NOTE: This operator appears to be parsed as left-associative, but errors
-    // if it is used in a non-associative manner.
-    binop!(compare_expr, bitor_expr, |input| {
-        if input.peek(Token![==]) {
-            Some(BinOp::Eq(input.parse()?))
-        } else if input.peek(Token![!=]) {
-            Some(BinOp::Ne(input.parse()?))
-        // must be before `<`
-        } else if input.peek(Token![<=]) {
-            Some(BinOp::Le(input.parse()?))
-        // must be before `>`
-        } else if input.peek(Token![>=]) {
-            Some(BinOp::Ge(input.parse()?))
-        } else if input.peek(Token![<]) && !input.peek(Token![<<]) && !input.peek(Token![<-]) {
-            Some(BinOp::Lt(input.parse()?))
-        } else if input.peek(Token![>]) && !input.peek(Token![>>]) {
-            Some(BinOp::Gt(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <bitxor> | <bitxor> ...
-    binop!(bitor_expr, bitxor_expr, |input| {
-        if input.peek(Token![|]) && !input.peek(Token![||]) && !input.peek(Token![|=]) {
-            Some(BinOp::BitOr(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <bitand> ^ <bitand> ...
-    binop!(bitxor_expr, bitand_expr, |input| {
-        if input.peek(Token![^]) && !input.peek(Token![^=]) {
-            Some(BinOp::BitXor(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <shift> & <shift> ...
-    binop!(bitand_expr, shift_expr, |input| {
-        if input.peek(Token![&]) && !input.peek(Token![&&]) && !input.peek(Token![&=]) {
-            Some(BinOp::BitAnd(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <arith> << <arith> ...
-    // <arith> >> <arith> ...
-    binop!(shift_expr, arith_expr, |input| {
-        if input.peek(Token![<<]) && !input.peek(Token![<<=]) {
-            Some(BinOp::Shl(input.parse()?))
-        } else if input.peek(Token![>>]) && !input.peek(Token![>>=]) {
-            Some(BinOp::Shr(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <term> + <term> ...
-    // <term> - <term> ...
-    binop!(arith_expr, term_expr, |input| {
-        if input.peek(Token![+]) && !input.peek(Token![+=]) {
-            Some(BinOp::Add(input.parse()?))
-        } else if input.peek(Token![-]) && !input.peek(Token![-=]) {
-            Some(BinOp::Sub(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <cast> * <cast> ...
-    // <cast> / <cast> ...
-    // <cast> % <cast> ...
-    binop!(term_expr, cast_expr, |input| {
-        if input.peek(Token![*]) && !input.peek(Token![*=]) {
-            Some(BinOp::Mul(input.parse()?))
-        } else if input.peek(Token![/]) && !input.peek(Token![/=]) {
-            Some(BinOp::Div(input.parse()?))
-        } else if input.peek(Token![%]) && !input.peek(Token![%=]) {
-            Some(BinOp::Rem(input.parse()?))
-        } else {
-            None
-        }
-    });
-
-    // <unary> as <ty>
-    // <unary> : <ty>
-    #[cfg(feature = "full")]
-    fn cast_expr(
-        input: ParseStream,
-        allow_struct: AllowStruct,
-        allow_block: AllowBlock,
-    ) -> Result<Expr> {
-        let mut e = unary_expr(input, allow_struct, allow_block)?;
-        loop {
-            if input.peek(Token![as]) {
-                e = Expr::Cast(ExprCast {
-                    attrs: Vec::new(),
-                    expr: Box::new(e),
-                    as_token: input.parse()?,
-                    // We can't accept `A + B` in cast expressions, as it's
-                    // ambiguous with the + expression.
-                    ty: Box::new(input.call(Type::without_plus)?),
-                });
-            } else if input.peek(Token![:]) {
-                e = Expr::Type(ExprType {
-                    attrs: Vec::new(),
-                    expr: Box::new(e),
-                    colon_token: input.parse()?,
-                    // We can't accept `A + B` in cast expressions, as it's
-                    // ambiguous with the + expression.
-                    ty: Box::new(input.call(Type::without_plus)?),
-                });
-            } else {
-                break;
-            }
-        }
-        Ok(e)
-    }
-
-    // <unary> as <ty>
-    #[cfg(not(feature = "full"))]
-    fn cast_expr(
-        input: ParseStream,
-        allow_struct: AllowStruct,
-        allow_block: AllowBlock,
-    ) -> Result<Expr> {
-        let mut e = unary_expr(input, allow_struct, allow_block)?;
-        while input.peek(Token![as]) {
-            e = Expr::Cast(ExprCast {
-                attrs: Vec::new(),
-                expr: Box::new(e),
-                as_token: input.parse()?,
-                // We can't accept `A + B` in cast expressions, as it's
-                // ambiguous with the + expression.
-                ty: Box::new(input.call(Type::without_plus)?),
-            });
-        }
-        Ok(e)
-    }
-
     // <UnOp> <trailer>
     // & <trailer>
     // &mut <trailer>
@@ -1498,6 +1353,16 @@
         let outer_attrs = take_outer(&mut attrs);
         e.replace_attrs(attrs);
 
+        e = trailer_helper(input, e)?;
+
+        let mut attrs = outer_attrs;
+        attrs.extend(e.replace_attrs(Vec::new()));
+        e.replace_attrs(attrs);
+        Ok(e)
+    }
+
+    #[cfg(feature = "full")]
+    fn trailer_helper(input: ParseStream, mut e: Expr) -> Result<Expr> {
         loop {
             if input.peek(token::Paren) {
                 let content;
@@ -1576,10 +1441,6 @@
                 break;
             }
         }
-
-        let mut attrs = outer_attrs;
-        attrs.extend(e.replace_attrs(Vec::new()));
-        e.replace_attrs(attrs);
         Ok(e)
     }
 
@@ -1695,36 +1556,58 @@
     ));
 
     #[cfg(feature = "full")]
-    named2!(expr_nosemi -> Expr, do_parse!(
-        nosemi: alt!(
-            syn!(ExprIf) => { Expr::If }
-            |
-            syn!(ExprIfLet) => { Expr::IfLet }
-            |
-            syn!(ExprWhile) => { Expr::While }
-            |
-            syn!(ExprWhileLet) => { Expr::WhileLet }
-            |
-            syn!(ExprForLoop) => { Expr::ForLoop }
-            |
-            syn!(ExprLoop) => { Expr::Loop }
-            |
-            syn!(ExprMatch) => { Expr::Match }
-            |
-            syn!(ExprTryBlock) => { Expr::TryBlock }
-            |
-            syn!(ExprYield) => { Expr::Yield }
-            |
-            syn!(ExprUnsafe) => { Expr::Unsafe }
-            |
-            syn!(ExprBlock) => { Expr::Block }
-        ) >>
-        // If the next token is a `.` or a `?` it is special-cased to parse
-        // as an expression instead of a blockexpression.
-        not!(punct!(.)) >>
-        not!(punct!(?)) >>
-        (nosemi)
-    ));
+    fn expr_early(input: ParseStream) -> Result<Expr> {
+        let mut attrs = input.call(Attribute::parse_outer)?;
+        let mut expr = if input.peek(Token![if]) {
+            if input.peek2(Token![let]) {
+                Expr::IfLet(input.parse()?)
+            } else {
+                Expr::If(input.parse()?)
+            }
+        } else if input.peek(Token![while]) {
+            if input.peek2(Token![let]) {
+                Expr::WhileLet(input.parse()?)
+            } else {
+                Expr::While(input.parse()?)
+            }
+        } else if input.peek(Token![for]) {
+            Expr::ForLoop(input.parse()?)
+        } else if input.peek(Token![loop]) {
+            Expr::Loop(input.parse()?)
+        } else if input.peek(Token![match]) {
+            Expr::Match(input.parse()?)
+        } else if input.peek(Token![try]) && input.peek2(token::Brace) {
+            Expr::TryBlock(input.parse()?)
+        } else if input.peek(Token![unsafe]) {
+            Expr::Unsafe(input.parse()?)
+        } else if input.peek(token::Brace) {
+            Expr::Block(input.parse()?)
+        } else {
+            let allow_struct = AllowStruct(true);
+            let allow_block = AllowBlock(true);
+            let mut expr = unary_expr(input, allow_struct, allow_block)?;
+
+            attrs.extend(expr.replace_attrs(Vec::new()));
+            expr.replace_attrs(attrs);
+
+            return parse_expr(input, expr, allow_struct, allow_block, Precedence::Any);
+        };
+
+        if input.peek(Token![.]) || input.peek(Token![?]) {
+            expr = trailer_helper(input, expr)?;
+
+            attrs.extend(expr.replace_attrs(Vec::new()));
+            expr.replace_attrs(attrs);
+
+            let allow_struct = AllowStruct(true);
+            let allow_block = AllowBlock(true);
+            return parse_expr(input, expr, allow_struct, allow_block, Precedence::Any);
+        }
+
+        attrs.extend(expr.replace_attrs(Vec::new()));
+        expr.replace_attrs(attrs);
+        Ok(expr)
+    }
 
     impl Parse for ExprLit {
         #[cfg(not(feature = "full"))]
@@ -2084,11 +1967,7 @@
                 },
                 fat_arrow_token: input.parse()?,
                 body: {
-                    let body = if input.fork().call(expr_nosemi).is_ok() {
-                        input.call(expr_nosemi)?
-                    } else {
-                        input.parse()?
-                    };
+                    let body = input.call(expr_early)?;
                     requires_comma = arm_expr_requires_comma(&body);
                     Box::new(body)
                 },
@@ -2629,12 +2508,8 @@
                 || ahead.peek(Token![macro])
             {
                 input.parse().map(Stmt::Item)
-            } else if ahead.fork().call(stmt_blockexpr).is_ok() {
-                stmt_blockexpr(input)
-            } else if ahead.call(stmt_expr).is_ok() {
-                stmt_expr(input)
             } else {
-                Err(input.error("expected statement"))
+                input.call(stmt_expr)
             }
         }
     }
@@ -2701,30 +2576,33 @@
     }
 
     #[cfg(feature = "full")]
-    fn stmt_blockexpr(input: ParseStream) -> Result<Stmt> {
+    fn stmt_expr(input: ParseStream) -> Result<Stmt> {
         let mut attrs = input.call(Attribute::parse_outer)?;
-        let mut e = expr_nosemi(input)?;
+        let mut e = expr_early(input)?;
 
         attrs.extend(e.replace_attrs(Vec::new()));
         e.replace_attrs(attrs);
 
         if input.peek(Token![;]) {
-            Ok(Stmt::Semi(e, input.parse()?))
-        } else {
-            Ok(Stmt::Expr(e))
+            return Ok(Stmt::Semi(e, input.parse()?));
         }
-    }
 
-    #[cfg(feature = "full")]
-    fn stmt_expr(input: ParseStream) -> Result<Stmt> {
-        let mut attrs = input.call(Attribute::parse_outer)?;
-        let mut e: Expr = input.parse()?;
-        let semi_token: Token![;] = input.parse()?;
-
-        attrs.extend(e.replace_attrs(Vec::new()));
-        e.replace_attrs(attrs);
-
-        Ok(Stmt::Semi(e, semi_token))
+        match e {
+            Expr::IfLet(_) |
+            Expr::If(_) |
+            Expr::WhileLet(_) |
+            Expr::While(_) |
+            Expr::ForLoop(_) |
+            Expr::Loop(_) |
+            Expr::Match(_) |
+            Expr::TryBlock(_) |
+            Expr::Yield(_) |
+            Expr::Unsafe(_) |
+            Expr::Block(_) => Ok(Stmt::Expr(e)),
+            _ => {
+                Err(input.error("expected semicolon"))
+            }
+        }
     }
 
     #[cfg(feature = "full")]