├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── call.rs ├── normal.rs └── override_builtins.rs ├── src ├── convert.rs ├── expand.rs └── lib.rs └── tests └── normal.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | addons: 5 | apt: 6 | packages: 7 | - libcurl4-openssl-dev 8 | - libelf-dev 9 | - libdw-dev 10 | 11 | rust: 12 | - nightly 13 | # - beta 14 | # - stable 15 | 16 | before_script: 17 | - | 18 | pip install 'travis-cargo<0.2' --user && 19 | export PATH=$HOME/.local/bin:$PATH 20 | 21 | script: 22 | - | 23 | travis-cargo build && 24 | travis-cargo test && 25 | travis-cargo bench && 26 | travis-cargo --only "${DOC_UPLOAD_VERSION}" doc 27 | 28 | after_success: 29 | - travis-cargo --only "${DOC_UPLOAD_VERSION}" doc-upload 30 | - travis-cargo coveralls --no-sudo 31 | 32 | env: 33 | global: 34 | - TRAVIS_CARGO_NIGHTLY_FEATURE="" 35 | - DOC_UPLOAD_VERSION=nightly 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "power-assert" 3 | version = "0.3.9" 4 | authors = ["gifnksm "] 5 | license = "MIT" 6 | readme = "README.md" 7 | repository = "https://github.com/gifnksm/power-assert-rs" 8 | description = "Power Assert in Rust. Provides better assertion message." 9 | 10 | [lib] 11 | name = "power_assert" 12 | path = "src/lib.rs" 13 | plugin = true 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 NAKASHIMA, Makoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # power-assert-rs 2 | 3 | [![Build Status](https://travis-ci.org/gifnksm/power-assert-rs.svg)](https://travis-ci.org/gifnksm/power-assert-rs) 4 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 5 | [![crates.io](http://meritbadge.herokuapp.com/power-assert)](https://crates.io/crates/power-assert) 6 | 7 | Power Assert in Rust. Provides better assertion message like this: 8 | 9 | ``` 10 | $ cargo run --example normal 11 | Running `target/debug/examples/normal` 12 | thread '
' panicked at 'assertion failed: bar.val == bar.foo.val 13 | power_assert!(bar.val == bar.foo.val) 14 | | | | | | | 15 | | 3 | | | 2 16 | | | | Foo { val: 2 } 17 | | | Bar { val: 3, foo: Foo { val: 2 } } 18 | | false 19 | Bar { val: 3, foo: Foo { val: 2 } } 20 | ', examples/normal.rs:26 21 | An unknown error occurred 22 | 23 | To learn more, run the command again with --verbose. 24 | ``` 25 | 26 | # How to use 27 | 28 | Add this to your `Cargo.toml`: 29 | 30 | ```toml 31 | [dependencies] 32 | power-assert = "*" 33 | ``` 34 | 35 | and add this to your `lib.rs` or `main.rs`: 36 | 37 | ```rust 38 | #![feature(plugin)] 39 | #![plugin(power_assert)] 40 | ``` 41 | 42 | Now, you can use `power_assert!()` and `power_assert_eq!()`. 43 | 44 | If you want to override builtin `assert!()` and `assert_eq!()`, change your `lib.rs` or `main.rs` as follows. 45 | 46 | ```rust 47 | #![feature(plugin)] 48 | #![plugin(power_assert(override_builtins))] 49 | ``` 50 | -------------------------------------------------------------------------------- /examples/call.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_copy_implementations)] 2 | #![warn(missing_debug_implementations)] 3 | #![warn(trivial_casts)] 4 | #![warn(trivial_numeric_casts)] 5 | #![warn(unused_extern_crates)] 6 | #![warn(unused_import_braces)] 7 | #![warn(unused_qualifications)] 8 | #![warn(unused_results)] 9 | 10 | #![feature(plugin)] 11 | #![plugin(power_assert)] 12 | 13 | #[derive(Debug, Eq, PartialEq)] 14 | struct Foo { 15 | val: i32 16 | } 17 | 18 | #[derive(Debug, Eq, PartialEq)] 19 | struct Bar { 20 | val: i32, 21 | foo: Foo 22 | } 23 | 24 | fn get_bar(val1: i32, val2: i32) -> Bar { 25 | Bar { val: val1, foo: Foo { val: val2 }} 26 | } 27 | 28 | fn main() { 29 | power_assert!(get_bar(3 + 5, -(3 + 1)) == get_bar(4 * 2, 3 * 3 / 3)); 30 | power_assert_eq!(get_bar(3 + 5, -(3 + 1)), get_bar(4 * 2, 3 * 3 / 3)); 31 | } 32 | -------------------------------------------------------------------------------- /examples/normal.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_copy_implementations)] 2 | #![warn(missing_debug_implementations)] 3 | #![warn(trivial_casts)] 4 | #![warn(trivial_numeric_casts)] 5 | #![warn(unused_extern_crates)] 6 | #![warn(unused_import_braces)] 7 | #![warn(unused_qualifications)] 8 | #![warn(unused_results)] 9 | 10 | #![feature(plugin)] 11 | #![plugin(power_assert)] 12 | 13 | #[derive(Debug)] 14 | struct Foo { 15 | val: u32 16 | } 17 | 18 | #[derive(Debug)] 19 | struct Bar { 20 | val: u32, 21 | foo: Foo 22 | } 23 | 24 | fn main() { 25 | let bar = Bar { val: 3, foo: Foo { val: 2 }}; 26 | power_assert!(bar.val == bar.foo.val); 27 | power_assert_eq!(bar.val, bar.foo.val); 28 | } 29 | -------------------------------------------------------------------------------- /examples/override_builtins.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_copy_implementations)] 2 | #![warn(missing_debug_implementations)] 3 | #![warn(trivial_casts)] 4 | #![warn(trivial_numeric_casts)] 5 | #![warn(unused_extern_crates)] 6 | #![warn(unused_import_braces)] 7 | #![warn(unused_qualifications)] 8 | #![warn(unused_results)] 9 | 10 | #![feature(plugin)] 11 | #![plugin(power_assert(override_builtins))] 12 | 13 | #[derive(Debug)] 14 | struct Foo { 15 | val: u32 16 | } 17 | 18 | #[derive(Debug)] 19 | struct Bar { 20 | val: u32, 21 | foo: Foo 22 | } 23 | 24 | fn main() { 25 | let bar = Bar { val: 3, foo: Foo { val: 2 }}; 26 | assert!(bar.val == bar.foo.val); 27 | assert_eq!(bar.val, bar.foo.val); 28 | } 29 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::collections::hash_map::Entry; 3 | use syntax::ast::{Expr, Ident, TokenTree}; 4 | use syntax::codemap::{BytePos, CodeMap}; 5 | use syntax::ext::base::ExtCtxt; 6 | use syntax::ext::quote::rt::ToTokens; 7 | use syntax::fold::{self, Folder}; 8 | use syntax::parse::{self, token}; 9 | use syntax::ptr::P; 10 | use syntax::print::pprust; 11 | 12 | fn register_pos(map: &mut HashMap, org_pos: BytePos, pp_pos: i32) { 13 | match map.entry(org_pos) { 14 | Entry::Occupied(e) => { 15 | assert_eq!(e.get(), &pp_pos); 16 | } 17 | Entry::Vacant(e) => { 18 | let _ = e.insert(pp_pos); 19 | } 20 | } 21 | } 22 | 23 | fn register_pos_rec(map: &mut HashMap, 24 | codemap: &CodeMap, 25 | tt: &TokenTree, 26 | ptt: &TokenTree) { 27 | let line = codemap.span_to_lines(ptt.get_span()).unwrap(); 28 | register_pos(map, tt.get_span().lo, line.lines[0].start_col.0 as i32); 29 | 30 | assert_eq!(tt.len(), ptt.len()); 31 | for i in 0..tt.len() { 32 | register_pos_rec(map, codemap, &tt.get_tt(i), &ptt.get_tt(i)); 33 | } 34 | } 35 | 36 | fn create_pos_map(cx: &mut ExtCtxt, 37 | ppstr: String, 38 | original_tokens: &[TokenTree]) 39 | -> HashMap { 40 | let codemap = cx.parse_sess().codemap(); 41 | let filemap = codemap.new_filemap("".to_string(), ppstr); 42 | let ptokens = parse::filemap_to_tts(cx.parse_sess(), filemap); 43 | 44 | let mut map = HashMap::new(); 45 | for (tt, ptt) in original_tokens.iter().zip(ptokens) { 46 | register_pos_rec(&mut map, &codemap, &tt, &ptt); 47 | } 48 | map 49 | } 50 | 51 | struct AssertFolder<'cx> { 52 | cx: &'cx ExtCtxt<'cx>, 53 | pos_map: &'cx HashMap, 54 | ident: Ident, 55 | push_count: usize, 56 | } 57 | 58 | impl<'cx> Folder for AssertFolder<'cx> { 59 | fn fold_expr(&mut self, expr: P) -> P { 60 | expr.and_then(|expr| { 61 | use syntax::ast::*; 62 | match expr.node { 63 | // ExprKind::Box 64 | // ExprKind::Vec 65 | ExprKind::Call(ref i, ref args) => { 66 | let col = self.pos_map.get(&i.span.lo).unwrap(); 67 | let args = args.iter() 68 | .map(|expr| self.fold_expr(expr.clone())) 69 | .collect::>(); 70 | let ident = self.ident; 71 | let mut call_expr = expr.clone(); 72 | call_expr.node = ExprKind::Call(i.clone(), args); 73 | 74 | let call_expr = P(call_expr); 75 | self.push_count += 1; 76 | quote_expr!(self.cx, { 77 | let expr = $call_expr; 78 | $ident.push(($col, format!("{:?}", expr))); 79 | expr 80 | }) 81 | } 82 | ExprKind::MethodCall(i, _, _) => { 83 | // TODO: fix column 84 | let col = self.pos_map.get(&i.span.lo).unwrap(); 85 | let conv_expr = P(fold::noop_fold_expr(expr, self)); 86 | let ident = self.ident; 87 | self.push_count += 1; 88 | quote_expr!(self.cx, { 89 | let expr = $conv_expr; 90 | $ident.push(($col, format!("{:?}", expr))); 91 | expr 92 | }) 93 | } 94 | // ExprKind::Tup 95 | ExprKind::Binary(op, _, _) => { 96 | let col = self.pos_map.get(&op.span.lo).unwrap(); 97 | let conv_expr = P(fold::noop_fold_expr(expr, self)); 98 | let ident = self.ident; 99 | self.push_count += 1; 100 | quote_expr!(self.cx, { 101 | let expr = $conv_expr; 102 | $ident.push(($col, format!("{:?}", expr))); 103 | expr 104 | }) 105 | } 106 | // ExprKind::Unary 107 | // ExprKind::Lit 108 | // ExprKind::Cast 109 | ExprKind::If(..) | 110 | ExprKind::IfLet(..) | 111 | ExprKind::Match(..) => { 112 | let col = self.pos_map.get(&expr.span.lo).unwrap(); 113 | let conv_expr = P(fold::noop_fold_expr(expr, self)); 114 | let ident = self.ident; 115 | self.push_count += 1; 116 | quote_expr!(self.cx, { 117 | let expr = $conv_expr; 118 | $ident.push(($col, format!("{:?}", expr))); 119 | expr 120 | }) 121 | } 122 | // ExprKind::While 123 | // ExprKind::WhileLet 124 | // ExprKind::ForLoop 125 | // ExprKind::Loop 126 | // ExprKind::Match: See above 127 | // ExprKind::Closure 128 | // ExprKind::Block 129 | // ExprKind::Assign 130 | // ExprKind::AssignOp 131 | ExprKind::Field(..) => self.convert_field(P(expr.clone())), 132 | ExprKind::TupField(..) => self.convert_field(P(expr.clone())), 133 | ExprKind::Index(_, ref i) => { 134 | // TODO: Fix column 135 | let col = self.pos_map.get(&i.span.lo).unwrap(); 136 | let conv_expr = P(fold::noop_fold_expr(expr.clone(), self)); 137 | let ident = self.ident; 138 | self.push_count += 1; 139 | quote_expr!(self.cx, { 140 | let expr = $conv_expr; 141 | $ident.push(($col, format!("{:?}", expr))); 142 | expr 143 | }) 144 | } 145 | // ExprKind::Range 146 | ExprKind::Path(..) => { 147 | let col = self.pos_map.get(&expr.span.lo).unwrap(); 148 | let expr = P(expr); 149 | let ident = self.ident; 150 | self.push_count += 1; 151 | quote_expr!(self.cx, { 152 | $ident.push(($col, format!("{:?}", $expr))); 153 | $expr 154 | }) 155 | } 156 | // ExprKind::AddrOf 157 | // ExprKind::Break 158 | // ExprKind::Again 159 | // ExprKind::Ret 160 | // ExprKind::InlineAsm 161 | // ExprKind::Mac 162 | // ExprKind::Struct 163 | // ExprKind::Repeat 164 | // ExprKind::Paren 165 | _ => P(fold::noop_fold_expr(expr, self)), 166 | } 167 | }) 168 | } 169 | } 170 | 171 | impl<'cx> AssertFolder<'cx> { 172 | fn convert_field(&mut self, expr: P) -> P { 173 | use syntax::ast::*; 174 | let mut exprs = vec![]; 175 | let mut cur_expr = expr.clone(); 176 | let prelude; 177 | let ident; 178 | loop { 179 | cur_expr = match cur_expr.node { 180 | ExprKind::Field(ref e, _) | ExprKind::TupField(ref e, _) => { 181 | exprs.push(cur_expr.clone()); 182 | e.clone() 183 | } 184 | ExprKind::Path(..) => { 185 | ident = cur_expr.clone(); 186 | let id = self.ident; 187 | let col = self.pos_map.get(&expr.span.lo).unwrap(); 188 | self.push_count += 1; 189 | prelude = quote_stmt!(self.cx, $id.push(($col, format!("{:?}", $ident)))); 190 | break; 191 | } 192 | _ => { 193 | let id = token::gensym_ident("a"); 194 | ident = quote_expr!(self.cx, $id); 195 | let conv_expr = P(fold::noop_fold_expr((*cur_expr).clone(), self)); 196 | prelude = quote_stmt!(self.cx, let $id = $conv_expr;); 197 | break; 198 | } 199 | } 200 | } 201 | exprs.reverse(); 202 | let exprs = exprs.into_iter() 203 | .scan(ident, |st, item| { 204 | let mut item = (*item).clone(); 205 | let col = match item.node { 206 | ExprKind::Field(_, i) => { 207 | item.node = ExprKind::Field(st.clone(), i); 208 | self.pos_map.get(&i.span.lo).unwrap() 209 | } 210 | ExprKind::TupField(_, i) => { 211 | item.node = ExprKind::TupField(st.clone(), i); 212 | self.pos_map.get(&i.span.lo).unwrap() 213 | } 214 | _ => panic!(), 215 | }; 216 | *st = P(item); 217 | Some((st.clone(), col)) 218 | }) 219 | .collect::>(); 220 | let last_expr = exprs[exprs.len() - 1].0.clone(); 221 | let mut stmts = vec![prelude]; 222 | for (e, col) in exprs { 223 | let ident = self.ident; 224 | self.push_count += 1; 225 | stmts.push(quote_stmt!(self.cx, $ident.push(($col, format!("{:?}", $e)));)); 226 | } 227 | quote_expr!(self.cx, { 228 | $stmts; 229 | $last_expr 230 | }) 231 | } 232 | } 233 | 234 | pub fn convert_expr(cx: &mut ExtCtxt, 235 | expr: P, 236 | ident: Ident, 237 | tts: &[TokenTree]) 238 | -> (P, bool) { 239 | // Pretty-printed snippet is used for showing assersion failed message, but 240 | // its tokens may have different spans from original code snippet's tokens. 241 | // Power-assert using those spans, so, parse the pretty-printed snippet 242 | // again to get tokens that have the same spans with pretty-printed snippet. 243 | let ppexpr_str = pprust::expr_to_string(&expr); 244 | let pos_map = create_pos_map(cx, ppexpr_str, tts); 245 | 246 | // Convert 247 | let mut folder = AssertFolder { 248 | cx: cx, 249 | pos_map: &pos_map, 250 | ident: ident, 251 | push_count: 0, 252 | }; 253 | (folder.fold_expr(expr), folder.push_count > 0) 254 | } 255 | -------------------------------------------------------------------------------- /src/expand.rs: -------------------------------------------------------------------------------- 1 | use convert; 2 | 3 | use syntax::ast::{Expr, Ident, TokenTree, Stmt}; 4 | use syntax::codemap::Span; 5 | use syntax::ext::base::{ExtCtxt, MacResult, MacEager}; 6 | use syntax::ext::quote::rt::ToTokens; 7 | use syntax::parse::token; 8 | use syntax::ptr::P; 9 | use syntax::print::pprust; 10 | 11 | // Copy from libsyntax 12 | // 13 | // A variant of 'try!' that panics on Err(FatalError). This is used as a 14 | // crutch on the way towards a non-panic!-prone parser. It should be used 15 | // for fatal parsing errors; eventually we plan to convert all code using 16 | // panictry to just use normal try 17 | macro_rules! panictry { 18 | ($e:expr) => ({ 19 | use std::result::Result::{Ok, Err}; 20 | use syntax::errors::FatalError; 21 | match $e { 22 | Ok(e) => e, 23 | Err(mut e) => { 24 | e.emit(); 25 | panic!(FatalError); 26 | } 27 | } 28 | }) 29 | } 30 | 31 | fn filter_tts_by_span(span: Span, tts: &[TokenTree]) -> Vec { 32 | tts.iter() 33 | .filter(|tt| span.lo <= tt.get_span().lo && tt.get_span().hi <= span.hi) 34 | .cloned() 35 | .collect() 36 | } 37 | 38 | fn expr_prelude(cx: &ExtCtxt) -> Vec { 39 | let use_stmt = quote_stmt!(cx, use std::io::Write;); 40 | let fn_stmt = quote_stmt!( 41 | cx, 42 | fn inspect(writer: &mut T, mut vals: Vec<(i32, String)>, offset: i32, repr: &str) { 43 | fn width(s: &str) -> i32 { s.len() as i32 } 44 | fn align(writer: &mut T, cur: &mut i32, col: i32, s: &str) { 45 | while *cur < col { 46 | let _ = write!(writer, " "); 47 | *cur += 1; 48 | } 49 | let _ = write!(writer, "{}", s); 50 | *cur += width(s); 51 | } 52 | 53 | vals.sort(); 54 | 55 | let _ = writeln!(writer, "{}", repr); 56 | { 57 | let mut cur = 0; 58 | for &(c, _) in &vals { 59 | align(writer, &mut cur, offset + c, "|"); 60 | } 61 | let _ = writeln!(writer, ""); 62 | } 63 | while !vals.is_empty() { 64 | let mut cur = 0; 65 | let mut i = 0; 66 | while i < vals.len() { 67 | if i == vals.len() - 1 || 68 | vals[i].0 + width(&vals[i].1) < vals[i + 1].0 { 69 | align(writer, &mut cur, offset + vals[i].0, &vals[i].1); 70 | let _ = vals.remove(i); 71 | } else { 72 | align(writer, &mut cur, offset + vals[i].0, "|"); 73 | i += 1; 74 | } 75 | } 76 | let _ = writeln!(writer, ""); 77 | } 78 | }); 79 | let write_stmt = quote_stmt!(cx, let mut write_buf = vec![];); 80 | 81 | vec![use_stmt.unwrap(), fn_stmt.unwrap(), write_stmt.unwrap()] 82 | } 83 | 84 | struct ExprGen { 85 | ident: Ident, 86 | tts: Vec, 87 | expr: P, 88 | ppstr: String, 89 | } 90 | 91 | impl ExprGen { 92 | fn new(ident: &str, expr: P, tts: &[TokenTree]) -> ExprGen { 93 | let tts = filter_tts_by_span(expr.span, tts); 94 | let ppstr = pprust::expr_to_string(&expr); 95 | 96 | ExprGen { 97 | ident: token::gensym_ident(ident), 98 | tts: tts, 99 | expr: expr, 100 | ppstr: ppstr, 101 | } 102 | } 103 | 104 | fn ppstr(&self) -> &str { 105 | &self.ppstr 106 | } 107 | 108 | fn init_stmt(&self, cx: &mut ExtCtxt, pushed: bool) -> P { 109 | let ident = self.ident; 110 | if pushed { 111 | P(quote_stmt!(cx, let mut $ident = vec![];).unwrap()) 112 | } else { 113 | P(quote_stmt!(cx, let $ident = vec![];).unwrap()) 114 | } 115 | } 116 | 117 | fn converted_expr(&self, cx: &mut ExtCtxt) -> (P, bool) { 118 | convert::convert_expr(cx, self.expr.clone(), self.ident, &self.tts) 119 | } 120 | 121 | fn inspect_expr(&self, cx: &mut ExtCtxt, left: &str, right: &str) -> P { 122 | let offset = left.len() as i32; 123 | let repr = format!("{}{}{}", left, self.ppstr, right); 124 | let ident = self.ident; 125 | quote_expr!(cx, inspect(&mut write_buf, $ident, $offset, $repr)) 126 | } 127 | } 128 | 129 | 130 | pub fn expand_assert(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box { 131 | let mut parser = cx.new_parser_from_tts(args); 132 | let cond_expr = panictry!(parser.parse_expr()); 133 | 134 | let msg_tts = if parser.eat(&token::Token::Comma) { 135 | let mut span = sp.clone(); 136 | span.lo = parser.span.lo; 137 | Some(filter_tts_by_span(span, args)) 138 | } else { 139 | None 140 | }; 141 | 142 | let prelude = expr_prelude(cx); 143 | 144 | let gen = ExprGen::new("vals", cond_expr, args); 145 | let inspect = gen.inspect_expr(cx, "power_assert!(", ")"); 146 | let (converted, pushed) = gen.converted_expr(cx); 147 | let init = gen.init_stmt(cx, pushed); 148 | 149 | let panic_msg = if let Some(tts) = msg_tts { 150 | quote_expr!(cx, format!($tts)) 151 | } else { 152 | let msg = format!("assertion failed: {}", gen.ppstr()); 153 | quote_expr!(cx, $msg) 154 | }; 155 | 156 | let expr = quote_expr!(cx, { 157 | $init; 158 | let cond = $converted; 159 | if !cond { 160 | $prelude; 161 | let _ = writeln!(&mut write_buf, $panic_msg); 162 | $inspect; 163 | panic!(String::from_utf8(write_buf).unwrap()) 164 | } 165 | }); 166 | 167 | // println!("{:?}", expr); 168 | 169 | MacEager::expr(expr) 170 | } 171 | 172 | pub fn expand_assert_eq(cx: &mut ExtCtxt, 173 | _sp: Span, 174 | args: &[TokenTree]) 175 | -> Box { 176 | let mut parser = cx.new_parser_from_tts(args); 177 | let lhs = panictry!(parser.parse_expr()); 178 | panictry!(parser.expect(&token::Token::Comma)); 179 | let rhs = panictry!(parser.parse_expr()); 180 | 181 | let lhs_gen = ExprGen::new("lhs_vals", lhs, args); 182 | let rhs_gen = ExprGen::new("rhs_vals", rhs, args); 183 | 184 | let prelude = expr_prelude(cx); 185 | 186 | let lhs_inspect = lhs_gen.inspect_expr(cx, "left: ", ""); 187 | let rhs_inspect = rhs_gen.inspect_expr(cx, "right: ", ""); 188 | let (lhs_converted, lhs_pushed) = lhs_gen.converted_expr(cx); 189 | let (rhs_converted, rhs_pushed) = rhs_gen.converted_expr(cx); 190 | let lhs_init = lhs_gen.init_stmt(cx, lhs_pushed); 191 | let rhs_init = rhs_gen.init_stmt(cx, rhs_pushed); 192 | let assert_msg = format!("power_assert_eq!({}, {})", lhs_gen.ppstr(), rhs_gen.ppstr()); 193 | 194 | let expr = quote_expr!(cx, { 195 | $lhs_init; 196 | $rhs_init; 197 | match (&$lhs_converted, &$rhs_converted) { 198 | (left_val, right_val) => { 199 | if !(*left_val == *right_val) { 200 | $prelude; 201 | let _ = writeln!(&mut write_buf, "assertion failed: `(left == right)` \ 202 | (left: `{:?}`, right: `{:?}`)", *left_val, *right_val); 203 | let _ = writeln!(&mut write_buf, $assert_msg); 204 | $lhs_inspect; 205 | $rhs_inspect; 206 | panic!(String::from_utf8(write_buf).unwrap()); 207 | } 208 | } 209 | } 210 | }); 211 | 212 | // println!("{:?}", expr); 213 | 214 | MacEager::expr(expr) 215 | } 216 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_copy_implementations)] 2 | #![warn(missing_debug_implementations)] 3 | #![warn(trivial_casts)] 4 | #![warn(trivial_numeric_casts)] 5 | #![warn(unused_extern_crates)] 6 | #![warn(unused_import_braces)] 7 | #![warn(unused_qualifications)] 8 | #![warn(unused_results)] 9 | 10 | #![feature(plugin_registrar)] 11 | #![feature(rustc_private)] 12 | #![feature(quote)] 13 | 14 | extern crate syntax; 15 | extern crate rustc_plugin; 16 | 17 | use syntax::ast::MetaItemKind; 18 | use rustc_plugin::Registry; 19 | 20 | mod expand; 21 | mod convert; 22 | 23 | struct Arg { 24 | override_builtins: bool, 25 | } 26 | 27 | impl Default for Arg { 28 | fn default() -> Arg { 29 | Arg { override_builtins: false } 30 | } 31 | } 32 | 33 | impl Arg { 34 | fn parse(reg: &mut Registry) -> Arg { 35 | let mut result = Arg::default(); 36 | 37 | for arg in reg.args() { 38 | match arg.node { 39 | MetaItemKind::Word(ref ns) => { 40 | match &**ns { 41 | "override_builtins" => { 42 | result.override_builtins = true; 43 | } 44 | _ => { 45 | reg.sess.span_err(arg.span, "invalid argument"); 46 | } 47 | } 48 | } 49 | _ => { 50 | reg.sess.span_err(arg.span, "invalid argument"); 51 | } 52 | } 53 | } 54 | 55 | result 56 | } 57 | } 58 | 59 | #[plugin_registrar] 60 | pub fn plugin_registrar(reg: &mut Registry) { 61 | let arg = Arg::parse(reg); 62 | 63 | if arg.override_builtins { 64 | reg.register_macro("assert", expand::expand_assert); 65 | reg.register_macro("assert_eq", expand::expand_assert_eq); 66 | } 67 | 68 | reg.register_macro("power_assert", expand::expand_assert); 69 | reg.register_macro("power_assert_eq", expand::expand_assert_eq); 70 | } 71 | -------------------------------------------------------------------------------- /tests/normal.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_copy_implementations)] 2 | #![warn(missing_debug_implementations)] 3 | #![warn(trivial_casts)] 4 | #![warn(trivial_numeric_casts)] 5 | #![warn(unused_extern_crates)] 6 | #![warn(unused_import_braces)] 7 | #![warn(unused_qualifications)] 8 | #![warn(unused_results)] 9 | 10 | #![feature(plugin)] 11 | #![plugin(power_assert)] 12 | 13 | use std::thread; 14 | 15 | #[derive(Debug)] 16 | struct Foo { 17 | val: u32 18 | } 19 | 20 | #[derive(Debug)] 21 | struct Bar { 22 | val: u32, 23 | foo: Foo 24 | } 25 | 26 | fn run_panic_test(f: F) -> String 27 | where F: FnOnce(), F: Send + 'static 28 | { 29 | thread::spawn(f).join().unwrap_err().downcast_ref::().unwrap().clone() 30 | } 31 | 32 | #[test] 33 | fn test_member_access() { 34 | assert_eq!(run_panic_test(|| { 35 | let bar = Bar { val: 3, foo: Foo { val: 2 }}; 36 | power_assert!(bar.val == bar.foo.val); 37 | }), "assertion failed: bar.val == bar.foo.val 38 | power_assert!(bar.val == bar.foo.val) 39 | | | | | | | 40 | | 3 | | | 2 41 | | | | Foo { val: 2 } 42 | | | Bar { val: 3, foo: Foo { val: 2 } } 43 | | false 44 | Bar { val: 3, foo: Foo { val: 2 } } 45 | "); 46 | 47 | assert_eq!(run_panic_test(|| { 48 | let bar = Bar { val: 3, foo: Foo { val: 2 }}; 49 | power_assert_eq!(bar.val, bar.foo.val); 50 | }), "assertion failed: `(left == right)` (left: `3`, right: `2`) 51 | power_assert_eq!(bar.val, bar.foo.val) 52 | left: bar.val 53 | | | 54 | | 3 55 | Bar { val: 3, foo: Foo { val: 2 } } 56 | right: bar.foo.val 57 | | | | 58 | | | 2 59 | | Foo { val: 2 } 60 | Bar { val: 3, foo: Foo { val: 2 } } 61 | "); 62 | } 63 | 64 | #[test] 65 | fn issue4() { 66 | fn check(_: &str) -> Result<(), ()> { 67 | Ok(()) 68 | } 69 | 70 | let s = "hello".to_owned(); 71 | 72 | assert!(check(&s) == Ok(())); 73 | power_assert!(check(&s) == Ok(())); 74 | } 75 | 76 | #[test] 77 | fn issue5() { 78 | assert_eq!(2*2, 4); 79 | power_assert_eq!(2*2, 4); 80 | } 81 | --------------------------------------------------------------------------------