├── .gitignore ├── README.md ├── auto-gc-test ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── auto-gc ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── extract-types ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs └── run-compiler ├── Cargo.lock ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Type-directed metaprogramming in Rust 2 | 3 | Materials accompanying the note ["Type-directed metaprogramming in Rust."](http://willcrichton.net/notes/type-directed-metaprogramming-in-rust/) Each subdirectory is its own crate, and will be need to be run in nightly. For example: 4 | 5 | ``` 6 | $ cd run-compiler 7 | $ rustup override set nightly-2018-03-19 8 | $ cargo run 9 | ``` 10 | 11 | The order is the same in the blog post: `run-compiler`, `extract-types`, and `auto-gc`. 12 | -------------------------------------------------------------------------------- /auto-gc-test/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "auto-gc-test" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "auto_gc 0.1.0", 6 | ] 7 | 8 | [[package]] 9 | name = "auto_gc" 10 | version = "0.1.0" 11 | 12 | -------------------------------------------------------------------------------- /auto-gc-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auto-gc-test" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [dependencies] 7 | auto_gc = {path = "../auto-gc"} -------------------------------------------------------------------------------- /auto-gc-test/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro)] 2 | 3 | extern crate auto_gc; 4 | use auto_gc::auto_gc; 5 | use std::rc::Rc; 6 | 7 | fn main() { 8 | auto_gc! { 9 | let x = 1; 10 | let y = 1 + x; 11 | }; 12 | println!("{:?}", *y); 13 | } 14 | -------------------------------------------------------------------------------- /auto-gc/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "auto_gc" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /auto-gc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auto_gc" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /auto-gc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, quote, proc_macro, proc_macro_internals)] 2 | 3 | extern crate proc_macro; 4 | extern crate rustc; 5 | extern crate rustc_errors; 6 | extern crate rustc_metadata; 7 | extern crate rustc_driver; 8 | extern crate rustc_trans_utils; 9 | extern crate rustc_resolve; 10 | extern crate rustc_incremental; 11 | extern crate rustc_typeck as typeck; 12 | extern crate syntax_pos; 13 | extern crate syntax; 14 | 15 | use std::sync::mpsc; 16 | use std::rc::Rc; 17 | use std::path::PathBuf; 18 | 19 | use rustc::{ty, hir, session}; 20 | use rustc_errors::registry::Registry; 21 | use rustc_metadata::cstore::CStore; 22 | use rustc_driver::driver; 23 | use rustc_resolve::MakeGlobMap; 24 | use syntax::{ext, ast}; 25 | use syntax_pos::DUMMY_SP; 26 | use syntax::ptr::P; 27 | use syntax::ext::quote::rt::ToTokens; 28 | use syntax::{tokenstream, parse}; 29 | use syntax_pos::symbol::Ident; 30 | 31 | // NOTE: if you are seeing "error[E0463]: can't find crate for `std`" 32 | // then you need to change this SYSROOT to your specific machine's. 33 | static SYSROOT: &'static str = "/Users/will/.rustup/toolchains/nightly-x86_64-apple-darwin"; 34 | 35 | trait Folder<'a> { 36 | fn fold_expr(&mut self, expr: &'a hir::Expr) -> P; 37 | fn fold_stmt(&mut self, stmt: &'a hir::Stmt) -> ast::Stmt; 38 | } 39 | 40 | struct TestFolder<'a, 'tcx: 'a, 'ecx: 'a> { 41 | ecx: &'a ext::base::ExtCtxt<'ecx>, 42 | tcx: ty::TyCtxt<'a, 'tcx, 'tcx>, 43 | } 44 | 45 | impl<'a, 'tcx: 'a, 'ecx> Folder<'tcx> for TestFolder<'a, 'tcx, 'ecx> { 46 | fn fold_expr(&mut self, expr: &'tcx hir::Expr) -> P { 47 | use hir::{Expr_, BinOp_, QPath}; 48 | let new_expr = match expr.node { 49 | Expr_::ExprLit(ref lit) => quote_expr!(&self.ecx, $lit), 50 | Expr_::ExprPath(QPath::Resolved(_, ref path)) => { 51 | let ident = Ident::with_empty_ctxt(path.segments[0].name); 52 | quote_expr!(&self.ecx, *$ident) 53 | }, 54 | Expr_::ExprBinary(ref binop, ref e1, ref e2) => { 55 | let e1 = self.fold_expr(e1); 56 | let e2 = self.fold_expr(e2); 57 | match binop.node { 58 | BinOp_::BiAdd => quote_expr!(&self.ecx, *$e1 + *$e2), 59 | _ => panic!("Not yet implemented") 60 | } 61 | }, 62 | Expr_::ExprBlock(ref block) => { 63 | let new_block: Vec = 64 | block.stmts.iter().map(|stmt| self.fold_stmt(stmt)).collect(); 65 | quote_expr!(&self.ecx, {$new_block}) 66 | }, 67 | _ => panic!("Not yet implemented") 68 | }; 69 | quote_expr!(&self.ecx, Rc::new($new_expr)) 70 | } 71 | 72 | fn fold_stmt(&mut self, stmt: &'tcx hir::Stmt) -> ast::Stmt { 73 | use hir::{Stmt_, Decl_, PatKind}; 74 | use ty::TypeVariants; 75 | use ast::IntTy; 76 | match stmt.node { 77 | Stmt_::StmtDecl(ref decl, _) => { 78 | match decl.node { 79 | Decl_::DeclLocal(ref local) => { 80 | let name = if let PatKind::Binding(_, _, ref name, _) = local.pat.node { 81 | Ident::with_empty_ctxt(name.node) 82 | } else { 83 | panic!("Not yet implemented") 84 | }; 85 | 86 | let expr = &local.init.iter().next().unwrap(); 87 | let table = self.tcx.typeck_tables_of(expr.hir_id.owner_def_id()); 88 | let ty = table.node_id_to_type(expr.hir_id); 89 | let ty = match ty.sty { 90 | TypeVariants::TyInt(intty) => 91 | match intty { 92 | IntTy::I32 => quote_ty!(&self.ecx, i32), 93 | _ => panic!("Not yet implemented") 94 | } 95 | _ => panic!("Not yet implemented") 96 | }; 97 | 98 | let expr = self.fold_expr(expr); 99 | quote_stmt!(&self.ecx, let $name: Rc<$ty> = $expr;).unwrap() 100 | }, 101 | _ => panic!("Not yet implemented") 102 | } 103 | } 104 | _ => panic!("Not yet implemented") 105 | } 106 | } 107 | } 108 | 109 | 110 | #[allow(unused_variables)] 111 | #[proc_macro] 112 | pub fn auto_gc(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { 113 | let codemap = syntax::codemap::CodeMap::new(syntax::codemap::FilePathMapping::empty()); 114 | codemap.new_filemap(syntax_pos::FileName::Custom("".to_string()), "".to_string()); 115 | 116 | let mut opts = session::config::basic_options(); 117 | opts.maybe_sysroot = Some(PathBuf::from(SYSROOT)); 118 | let sess = session::build_session_with_codemap( 119 | opts, 120 | None, 121 | Registry::new(&[]), 122 | Rc::new(codemap), 123 | None, 124 | ); 125 | 126 | let cstore = CStore::new( 127 | rustc_trans_utils::trans_crate::MetadataOnlyTransCrate::new() 128 | .metadata_loader(), 129 | ); 130 | 131 | let mut resolver = ext::base::DummyResolver; 132 | let ecx = ext::base::ExtCtxt::new( 133 | &sess.parse_sess, 134 | ext::expand::ExpansionConfig::default("".to_string()), 135 | &mut resolver, 136 | ); 137 | 138 | let ts = proc_macro::__internal::token_stream_inner(ts); 139 | let mut parser = parse::stream_to_parser(&sess.parse_sess, ts); 140 | let mut stmts = Vec::new(); 141 | while let Some(stmt) = parser.parse_full_stmt(false).unwrap() 142 | { 143 | stmts.push(stmt); 144 | 145 | if parser.token == parse::token::Token::Eof { 146 | break; 147 | } 148 | } 149 | 150 | let krate = 151 | ast::Crate { 152 | module: ast::Mod { 153 | inner: DUMMY_SP, 154 | items: vec![ 155 | quote_item!(&ecx, fn main(){$stmts}).unwrap() 156 | ], 157 | }, 158 | attrs: vec![], 159 | span: DUMMY_SP, 160 | }; 161 | 162 | let driver::ExpansionResult { 163 | expanded_crate, 164 | defs, 165 | analysis, 166 | resolutions, 167 | mut hir_forest, 168 | } = driver::phase_2_configure_and_expand( 169 | &sess, 170 | &cstore, 171 | krate, 172 | None, 173 | "test", 174 | None, 175 | MakeGlobMap::No, 176 | |_| Ok(()), 177 | ).unwrap(); 178 | 179 | let arenas = ty::AllArenas::new(); 180 | 181 | let hir_map = hir::map::map_crate(&sess, &cstore, &mut hir_forest, &defs); 182 | 183 | let mut local_providers = ty::maps::Providers::default(); 184 | driver::default_provide(&mut local_providers); 185 | 186 | let mut extern_providers = local_providers; 187 | driver::default_provide_extern(&mut extern_providers); 188 | 189 | let query_result_on_disk_cache = rustc_incremental::load_query_result_cache(&sess); 190 | 191 | let (tx, _rx) = mpsc::channel(); 192 | 193 | let input = session::config::Input::Str { 194 | name: syntax_pos::FileName::Custom("".to_string()), 195 | input: "".to_string(), 196 | }; 197 | let output_filenames = driver::build_output_filenames(&input, &None, &None, &[], &sess); 198 | 199 | ty::TyCtxt::create_and_enter(&sess, &cstore, local_providers, extern_providers, &arenas, resolutions, hir_map, query_result_on_disk_cache, "", tx, &output_filenames, |tcx| { 200 | typeck::check_crate(tcx).expect("typeck failure"); 201 | let mut folder = TestFolder {ecx: &ecx, tcx: tcx}; 202 | let ts: tokenstream::TokenStream = 203 | match tcx.hir.krate().bodies.values().next().unwrap().value.node { 204 | hir::Expr_::ExprBlock(ref block) => block 205 | .stmts.iter() 206 | .flat_map(|stmt| folder.fold_stmt(stmt).to_tokens(&ecx)) 207 | .collect(), 208 | _ => unreachable!() 209 | }; 210 | proc_macro::__internal::token_stream_wrap(ts) 211 | }) 212 | } 213 | -------------------------------------------------------------------------------- /extract-types/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "rustc-type-examples" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /extract-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extract-types" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /extract-types/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, quote)] 2 | 3 | extern crate rustc; 4 | extern crate rustc_errors; 5 | extern crate rustc_metadata; 6 | extern crate rustc_driver; 7 | extern crate rustc_trans_utils; 8 | extern crate rustc_resolve; 9 | extern crate rustc_incremental; 10 | extern crate rustc_typeck as typeck; 11 | extern crate syntax_pos; 12 | extern crate syntax; 13 | 14 | use std::sync::mpsc; 15 | use std::rc::Rc; 16 | use std::path::PathBuf; 17 | 18 | use rustc::{ty, hir, session}; 19 | use rustc_errors::registry::Registry; 20 | use rustc_metadata::cstore::CStore; 21 | use rustc_driver::driver; 22 | use rustc_resolve::MakeGlobMap; 23 | use syntax::{ext, ast}; 24 | use syntax_pos::DUMMY_SP; 25 | use rustc::hir::intravisit; 26 | use rustc::hir::intravisit::Visitor; 27 | 28 | // NOTE: if you are seeing "error[E0463]: can't find crate for `std`" 29 | // then you need to change this SYSROOT to your specific machine's. 30 | static SYSROOT: &'static str = "/Users/will/.rustup/toolchains/nightly-x86_64-apple-darwin"; 31 | 32 | struct TestVisitor<'a, 'tcx: 'a> { 33 | tcx: ty::TyCtxt<'a, 'tcx, 'tcx>, 34 | } 35 | 36 | impl<'a, 'tcx: 'a> Visitor<'tcx> for TestVisitor<'a, 'tcx> { 37 | fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> { 38 | intravisit::NestedVisitorMap::OnlyBodies(&self.tcx.hir) 39 | } 40 | 41 | fn visit_expr(&mut self, expr: &'tcx hir::Expr) { 42 | let table = self.tcx.typeck_tables_of(expr.hir_id.owner_def_id()); 43 | let ty = table.node_id_to_type(expr.hir_id); 44 | println!("Node: {:?}, type: {:?}", expr, ty); 45 | intravisit::walk_expr(self, expr); 46 | } 47 | } 48 | 49 | #[allow(unused_variables)] 50 | fn main() { 51 | syntax::with_globals(|| { 52 | let codemap = syntax::codemap::CodeMap::new(syntax::codemap::FilePathMapping::empty()); 53 | codemap.new_filemap(syntax_pos::FileName::Custom("".to_string()), "".to_string()); 54 | 55 | let mut opts = session::config::basic_options(); 56 | opts.maybe_sysroot = Some(PathBuf::from(SYSROOT)); 57 | let sess = session::build_session_with_codemap( 58 | opts, 59 | None, 60 | Registry::new(&[]), 61 | Rc::new(codemap), 62 | None, 63 | ); 64 | 65 | let cstore = CStore::new( 66 | rustc_trans_utils::trans_crate::MetadataOnlyTransCrate::new() 67 | .metadata_loader(), 68 | ); 69 | 70 | let mut resolver = ext::base::DummyResolver; 71 | let cx = ext::base::ExtCtxt::new( 72 | &sess.parse_sess, 73 | ext::expand::ExpansionConfig::default("".to_string()), 74 | &mut resolver, 75 | ); 76 | let krate = 77 | ast::Crate { 78 | module: ast::Mod { 79 | inner: DUMMY_SP, 80 | items: vec![ 81 | quote_item!( 82 | &cx, 83 | fn main(){let x: i32 = 1 + 2;}).unwrap(), 84 | ], 85 | }, 86 | attrs: vec![], 87 | span: DUMMY_SP, 88 | }; 89 | 90 | 91 | let driver::ExpansionResult { 92 | expanded_crate, 93 | defs, 94 | analysis, 95 | resolutions, 96 | mut hir_forest, 97 | } = driver::phase_2_configure_and_expand( 98 | &sess, 99 | &cstore, 100 | krate, 101 | None, 102 | "test", 103 | None, 104 | MakeGlobMap::No, 105 | |_| Ok(()), 106 | ).unwrap(); 107 | 108 | let arenas = ty::AllArenas::new(); 109 | 110 | let hir_map = hir::map::map_crate(&sess, &cstore, &mut hir_forest, &defs); 111 | 112 | let mut local_providers = ty::maps::Providers::default(); 113 | driver::default_provide(&mut local_providers); 114 | 115 | let mut extern_providers = local_providers; 116 | driver::default_provide_extern(&mut extern_providers); 117 | 118 | let query_result_on_disk_cache = rustc_incremental::load_query_result_cache(&sess); 119 | 120 | let (tx, _rx) = mpsc::channel(); 121 | 122 | let input = session::config::Input::Str { 123 | name: syntax_pos::FileName::Custom("".to_string()), 124 | input: "".to_string(), 125 | }; 126 | let output_filenames = driver::build_output_filenames(&input, &None, &None, &[], &sess); 127 | 128 | ty::TyCtxt::create_and_enter(&sess, &cstore, local_providers, extern_providers, &arenas, resolutions, hir_map, query_result_on_disk_cache, "", tx, &output_filenames, |tcx| { 129 | typeck::check_crate(tcx).expect("typeck failure"); 130 | let mut visitor = TestVisitor {tcx: tcx}; 131 | tcx.hir.krate().visit_all_item_likes(&mut visitor.as_deep_visitor()); 132 | }); 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /run-compiler/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "rustc-type-examples" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /run-compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "run-compiler" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /run-compiler/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, quote)] 2 | 3 | extern crate rustc; 4 | extern crate rustc_errors; 5 | extern crate rustc_metadata; 6 | extern crate rustc_driver; 7 | extern crate rustc_trans_utils; 8 | extern crate rustc_resolve; 9 | extern crate rustc_incremental; 10 | extern crate rustc_typeck as typeck; 11 | extern crate syntax_pos; 12 | extern crate syntax; 13 | 14 | use std::sync::mpsc; 15 | use std::rc::Rc; 16 | use std::path::PathBuf; 17 | 18 | use rustc::{ty, hir, session}; 19 | use rustc_errors::registry::Registry; 20 | use rustc_metadata::cstore::CStore; 21 | use rustc_driver::driver; 22 | use rustc_resolve::MakeGlobMap; 23 | use syntax::{ext, ast}; 24 | use syntax_pos::DUMMY_SP; 25 | 26 | // NOTE: if you are seeing "error[E0463]: can't find crate for `std`" 27 | // then you need to change this SYSROOT to your specific machine's. 28 | static SYSROOT: &'static str = "/Users/will/.rustup/toolchains/nightly-x86_64-apple-darwin"; 29 | 30 | #[allow(unused_variables)] 31 | fn main() { 32 | syntax::with_globals(|| { 33 | let codemap = syntax::codemap::CodeMap::new(syntax::codemap::FilePathMapping::empty()); 34 | codemap.new_filemap(syntax_pos::FileName::Custom("".to_string()), "".to_string()); 35 | 36 | let mut opts = session::config::basic_options(); 37 | opts.maybe_sysroot = Some(PathBuf::from(SYSROOT)); 38 | let sess = session::build_session_with_codemap( 39 | opts, 40 | None, 41 | Registry::new(&[]), 42 | Rc::new(codemap), 43 | None, 44 | ); 45 | 46 | let cstore = CStore::new( 47 | rustc_trans_utils::trans_crate::MetadataOnlyTransCrate::new() 48 | .metadata_loader(), 49 | ); 50 | 51 | let mut resolver = ext::base::DummyResolver; 52 | let cx = ext::base::ExtCtxt::new( 53 | &sess.parse_sess, 54 | ext::expand::ExpansionConfig::default("".to_string()), 55 | &mut resolver, 56 | ); 57 | let krate = 58 | ast::Crate { 59 | module: ast::Mod { 60 | inner: DUMMY_SP, 61 | items: vec![ 62 | quote_item!( 63 | &cx, 64 | fn main(){let x: i32 = 1 + 2;}).unwrap(), 65 | ], 66 | }, 67 | attrs: vec![], 68 | span: DUMMY_SP, 69 | }; 70 | 71 | 72 | let driver::ExpansionResult { 73 | expanded_crate, 74 | defs, 75 | analysis, 76 | resolutions, 77 | mut hir_forest, 78 | } = driver::phase_2_configure_and_expand( 79 | &sess, 80 | &cstore, 81 | krate, 82 | None, 83 | "test", 84 | None, 85 | MakeGlobMap::No, 86 | |_| Ok(()), 87 | ).unwrap(); 88 | 89 | let arenas = ty::AllArenas::new(); 90 | 91 | let hir_map = hir::map::map_crate(&sess, &cstore, &mut hir_forest, &defs); 92 | 93 | let mut local_providers = ty::maps::Providers::default(); 94 | driver::default_provide(&mut local_providers); 95 | 96 | let mut extern_providers = local_providers; 97 | driver::default_provide_extern(&mut extern_providers); 98 | 99 | let query_result_on_disk_cache = rustc_incremental::load_query_result_cache(&sess); 100 | 101 | let (tx, _rx) = mpsc::channel(); 102 | 103 | let input = session::config::Input::Str { 104 | name: syntax_pos::FileName::Custom("".to_string()), 105 | input: "".to_string(), 106 | }; 107 | let output_filenames = driver::build_output_filenames(&input, &None, &None, &[], &sess); 108 | 109 | ty::TyCtxt::create_and_enter(&sess, &cstore, local_providers, extern_providers, &arenas, resolutions, hir_map, query_result_on_disk_cache, "", tx, &output_filenames, |tcx| { 110 | typeck::check_crate(tcx).expect("typeck failure"); 111 | println!("Type checked successfully!"); 112 | }); 113 | }) 114 | } 115 | --------------------------------------------------------------------------------