Skip to content

Commit 80a3f45

Browse files
committed
auto merge of #11151 : sfackler/rust/ext-crate, r=alexcrichton
This is a first pass on support for procedural macros that aren't hardcoded into libsyntax. It is **not yet ready to merge** but I've opened a PR to have a chance to discuss some open questions and implementation issues. Example ======= Here's a silly example showing off the basics: my_synext.rs ```rust #[feature(managed_boxes, globs, macro_registrar, macro_rules)]; extern mod syntax; use syntax::ast::{Name, token_tree}; use syntax::codemap::Span; use syntax::ext::base::*; use syntax::parse::token; #[macro_export] macro_rules! exported_macro (() => (2)) #[macro_registrar] pub fn macro_registrar(register: |Name, SyntaxExtension|) { register(token::intern(&"make_a_1"), NormalTT(@SyntaxExpanderTT { expander: SyntaxExpanderTTExpanderWithoutContext(expand_make_a_1), span: None, } as @SyntaxExpanderTTTrait, None)); } pub fn expand_make_a_1(cx: &mut ExtCtxt, sp: Span, tts: &[token_tree]) -> MacResult { if !tts.is_empty() { cx.span_fatal(sp, "make_a_1 takes no arguments"); } MRExpr(quote_expr!(cx, 1i)) } ``` main.rs: ```rust #[feature(phase)]; #[phase(syntax)] extern mod my_synext; fn main() { assert_eq!(1, make_a_1!()); assert_eq!(2, exported_macro!()); } ``` Overview ======= Crates that contain syntax extensions need to define a function with the following signature and annotation: ```rust #[macro_registrar] pub fn registrar(register: |ast::Name, ext::base::SyntaxExtension|) { ... } ``` that should call the `register` closure with each extension it defines. `macro_rules!` style macros can be tagged with `#[macro_export]` to be exported from the crate as well. Crates that wish to use externally loadable syntax extensions load them by adding the `#[phase(syntax)]` attribute to an `extern mod`. All extensions registered by the specified crate are loaded with the same scoping rules as `macro_rules!` macros. If you want to use a crate both for syntax extensions and normal linkage, you can use `#[phase(syntax, link)]`. Open questions =========== * ~~Does the `macro_crate` syntax make sense? It wraps an entire `extern mod` declaration which looks a bit weird but is nice in the sense that the crate lookup logic can be identical between normal external crates and external macro crates. If the `extern mod` syntax, changes, this will get it for free, etc.~~ Changed to a `phase` attribute. * ~~Is the magic name `macro_crate_registration` the right way to handle extension registration? It could alternatively be handled by a function annotated with `#[macro_registration]` I guess.~~ Switched to an attribute. * The crate loading logic lives inside of librustc, which means that the syntax extension infrastructure can't directly access it. I've worked around this by passing a `CrateLoader` trait object from the driver to libsyntax that can call back into the crate loading logic. It should be possible to pull things apart enough that this isn't necessary anymore, but it will be an enormous refactoring project. I think we'll need to create a couple of new libraries: libsynext libmetadata/ty and libmiddle. * Item decorator extensions can be loaded but the `deriving` decorator itself can't be extended so you'd need to do e.g. `#[deriving_MyTrait] #[deriving(Clone)]` instead of `#[deriving(MyTrait, Clone)]`. Is this something worth bothering with for now? Remaining work =========== - [x] ~~There is not yet support for rustdoc downloading and compiling referenced macro crates as it does for other referenced crates. This shouldn't be too hard I think.~~ - [x] ~~This is not testable at stage1 and sketchily testable at stages above that. The stage *n* rustc links against the stage *n-1* libsyntax and librustc. Unfortunately, crates in the test/auxiliary directory link against the stage *n* libstd, libextra, libsyntax, etc. This causes macro crates to fail to properly dynamically link into rustc since names end up being mangled slightly differently. In addition, when rustc is actually installed onto a system, there are actually do copies of libsyntax, libstd, etc: the ones that user code links against and a separate set from the previous stage that rustc itself uses. By this point in the bootstrap process, the two library versions *should probably* be binary compatible, but it doesn't seem like a sure thing. Fixing this is apparently hard, but necessary to properly cross compile as well and is being tracked in #11145.~~ The offending tests are ignored during `check-stage1-rpass` and `check-stage1-cfail`. When we get a snapshot that has this commit, I'll look into how feasible it'll be to get them working on stage1. - [x] ~~`macro_rules!` style macros aren't being exported. Now that the crate loading infrastructure is there, this should just require serializing the AST of the macros into the crate metadata and yanking them out again, but I'm not very familiar with that part of the compiler.~~ - [x] ~~The `macro_crate_registration` function isn't type-checked when it's loaded. I poked around in the `csearch` infrastructure a bit but didn't find any super obvious ways of checking the type of an item with a certain name. Fixing this may also eliminate the need to `#[no_mangle]` the registration function.~~ Now that the registration function is identified by an attribute, typechecking this will be like typechecking other annotated functions. - [x] ~~The dynamic libraries that are loaded are never unloaded. It shouldn't require too much work to tie the lifetime of the `DynamicLibrary` object to the `MapChain` that its extensions are loaded into.~~ - [x] ~~The compiler segfaults sometimes when loading external crates. The `DynamicLibrary` reference and code objects from that library are both put into the same hash table. When the table drops, due to the random ordering the library sometimes drops before the objects do. Once #11228 lands it'll be easy to fix this.~~
2 parents 6141662 + 328b47d commit 80a3f45

39 files changed

+873
-205
lines changed

src/compiletest/header.rs

+4
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,14 @@ pub fn is_test_ignored(config: &config, testfile: &Path) -> bool {
9090
fn xfail_target(config: &config) -> ~str {
9191
~"xfail-" + util::get_os(config.target)
9292
}
93+
fn xfail_stage(config: &config) -> ~str {
94+
~"xfail-" + config.stage_id.split('-').next().unwrap()
95+
}
9396
9497
let val = iter_header(testfile, |ln| {
9598
if parse_name_directive(ln, "xfail-test") { false }
9699
else if parse_name_directive(ln, xfail_target(config)) { false }
100+
else if parse_name_directive(ln, xfail_stage(config)) { false }
97101
else if config.mode == common::mode_pretty &&
98102
parse_name_directive(ln, "xfail-pretty") { false }
99103
else { true }

src/librustc/driver/driver.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use lib::llvm::{ContextRef, ModuleRef};
2020
use metadata::common::LinkMeta;
2121
use metadata::{creader, filesearch};
2222
use metadata::cstore::CStore;
23+
use metadata::creader::Loader;
2324
use metadata;
2425
use middle::{trans, freevars, kind, ty, typeck, lint, astencode, reachable};
2526
use middle;
@@ -41,6 +42,7 @@ use syntax::attr;
4142
use syntax::attr::{AttrMetaMethods};
4243
use syntax::codemap;
4344
use syntax::diagnostic;
45+
use syntax::ext::base::CrateLoader;
4446
use syntax::parse;
4547
use syntax::parse::token;
4648
use syntax::print::{pp, pprust};
@@ -163,6 +165,7 @@ pub fn phase_1_parse_input(sess: Session, cfg: ast::CrateConfig, input: &input)
163165
/// standard library and prelude.
164166
pub fn phase_2_configure_and_expand(sess: Session,
165167
cfg: ast::CrateConfig,
168+
loader: &mut CrateLoader,
166169
mut crate: ast::Crate)
167170
-> (ast::Crate, syntax::ast_map::Map) {
168171
let time_passes = sess.time_passes();
@@ -188,9 +191,14 @@ pub fn phase_2_configure_and_expand(sess: Session,
188191
crate = time(time_passes, "configuration 1", crate, |crate|
189192
front::config::strip_unconfigured_items(crate));
190193

191-
crate = time(time_passes, "expansion", crate, |crate|
192-
syntax::ext::expand::expand_crate(sess.parse_sess, cfg.clone(),
193-
crate));
194+
crate = time(time_passes, "expansion", crate, |crate| {
195+
syntax::ext::expand::expand_crate(sess.parse_sess,
196+
loader,
197+
cfg.clone(),
198+
crate)
199+
});
200+
// dump the syntax-time crates
201+
sess.cstore.reset();
194202

195203
// strip again, in case expansion added anything with a #[cfg].
196204
crate = time(time_passes, "configuration 2", crate, |crate|
@@ -248,6 +256,11 @@ pub fn phase_3_run_analysis_passes(sess: Session,
248256
time(time_passes, "looking for entry point", (),
249257
|_| middle::entry::find_entry_point(sess, crate, ast_map));
250258

259+
sess.macro_registrar_fn.with_mut(|r| *r =
260+
time(time_passes, "looking for macro registrar", (), |_|
261+
syntax::ext::registrar::find_macro_registrar(
262+
sess.span_diagnostic, crate)));
263+
251264
let freevars = time(time_passes, "freevar finding", (), |_|
252265
freevars::annotate_freevars(def_map, crate));
253266

@@ -491,7 +504,8 @@ pub fn compile_input(sess: Session, cfg: ast::CrateConfig, input: &input,
491504
let (expanded_crate, ast_map) = {
492505
let crate = phase_1_parse_input(sess, cfg.clone(), input);
493506
if stop_after_phase_1(sess) { return; }
494-
phase_2_configure_and_expand(sess, cfg, crate)
507+
let loader = &mut Loader::new(sess);
508+
phase_2_configure_and_expand(sess, cfg, loader, crate)
495509
};
496510
let outputs = build_output_filenames(input, outdir, output,
497511
expanded_crate.attrs, sess);
@@ -579,7 +593,8 @@ pub fn pretty_print_input(sess: Session,
579593

580594
let (crate, ast_map, is_expanded) = match ppm {
581595
PpmExpanded | PpmExpandedIdentified | PpmTyped => {
582-
let (crate, ast_map) = phase_2_configure_and_expand(sess, cfg, crate);
596+
let loader = &mut Loader::new(sess);
597+
let (crate, ast_map) = phase_2_configure_and_expand(sess, cfg, loader, crate);
583598
(crate, Some(ast_map), true)
584599
}
585600
_ => (crate, None, false)
@@ -912,6 +927,7 @@ pub fn build_session_(sopts: @session::options,
912927
// For a library crate, this is always none
913928
entry_fn: RefCell::new(None),
914929
entry_type: Cell::new(None),
930+
macro_registrar_fn: RefCell::new(None),
915931
span_diagnostic: span_diagnostic_handler,
916932
filesearch: filesearch,
917933
building_library: Cell::new(false),

src/librustc/driver/session.rs

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ pub struct Session_ {
210210
entry_fn: RefCell<Option<(NodeId, codemap::Span)>>,
211211
entry_type: Cell<Option<EntryFnType>>,
212212
span_diagnostic: @diagnostic::SpanHandler,
213+
macro_registrar_fn: RefCell<Option<ast::DefId>>,
213214
filesearch: @filesearch::FileSearch,
214215
building_library: Cell<bool>,
215216
working_dir: Path,

src/librustc/front/feature_gate.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ static KNOWN_FEATURES: &'static [(&'static str, Status)] = &[
4343
("non_ascii_idents", Active),
4444
("thread_local", Active),
4545
("link_args", Active),
46+
("phase", Active),
47+
("macro_registrar", Active),
4648

4749
// These are used to test this portion of the compiler, they don't actually
4850
// mean anything
@@ -114,7 +116,15 @@ impl Visitor<()> for Context {
114116
}
115117
}
116118
}
117-
_ => {}
119+
ast::ViewItemExternMod(..) => {
120+
for attr in i.attrs.iter() {
121+
if "phase" == attr.name() {
122+
self.gate_feature("phase", attr.span,
123+
"compile time crate loading is \
124+
experimental and possibly buggy");
125+
}
126+
}
127+
}
118128
}
119129
visit::walk_view_item(self, i, ())
120130
}
@@ -151,6 +161,14 @@ impl Visitor<()> for Context {
151161
}
152162
}
153163

164+
ast::ItemFn(..) => {
165+
if attr::contains_name(i.attrs, "macro_registrar") {
166+
self.gate_feature("macro_registrar", i.span,
167+
"cross-crate macro exports are \
168+
experimental and possibly buggy");
169+
}
170+
}
171+
154172
_ => {}
155173
}
156174

src/librustc/front/test.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use driver::session;
1515
use front::config;
1616
use front::std_inject::with_version;
17+
use metadata::creader::Loader;
1718

1819
use std::cell::RefCell;
1920
use std::vec;
@@ -38,10 +39,10 @@ struct Test {
3839
should_fail: bool
3940
}
4041

41-
struct TestCtxt {
42+
struct TestCtxt<'a> {
4243
sess: session::Session,
4344
path: RefCell<~[ast::Ident]>,
44-
ext_cx: ExtCtxt,
45+
ext_cx: ExtCtxt<'a>,
4546
testfns: RefCell<~[Test]>,
4647
is_extra: bool,
4748
config: ast::CrateConfig,
@@ -63,11 +64,11 @@ pub fn modify_for_testing(sess: session::Session,
6364
}
6465
}
6566

66-
struct TestHarnessGenerator {
67-
cx: TestCtxt,
67+
struct TestHarnessGenerator<'a> {
68+
cx: TestCtxt<'a>,
6869
}
6970

70-
impl fold::Folder for TestHarnessGenerator {
71+
impl<'a> fold::Folder for TestHarnessGenerator<'a> {
7172
fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate {
7273
let folded = fold::noop_fold_crate(c, self);
7374

@@ -155,9 +156,10 @@ impl fold::Folder for TestHarnessGenerator {
155156

156157
fn generate_test_harness(sess: session::Session, crate: ast::Crate)
157158
-> ast::Crate {
159+
let loader = &mut Loader::new(sess);
158160
let mut cx: TestCtxt = TestCtxt {
159161
sess: sess,
160-
ext_cx: ExtCtxt::new(sess.parse_sess, sess.opts.cfg.clone()),
162+
ext_cx: ExtCtxt::new(sess.parse_sess, sess.opts.cfg.clone(), loader),
161163
path: RefCell::new(~[]),
162164
testfns: RefCell::new(~[]),
163165
is_extra: is_extra(&crate),

src/librustc/metadata/common.rs

+4
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ pub static tag_native_libraries_lib: uint = 0x104;
204204
pub static tag_native_libraries_name: uint = 0x105;
205205
pub static tag_native_libraries_kind: uint = 0x106;
206206

207+
pub static tag_macro_registrar_fn: uint = 0x110;
208+
pub static tag_exported_macros: uint = 0x111;
209+
pub static tag_macro_def: uint = 0x112;
210+
207211
#[deriving(Clone)]
208212
pub struct LinkMeta {
209213
crateid: CrateId,

src/librustc/metadata/creader.rs

+105-30
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010

1111
//! Validates all used crates and extern libraries and loads their metadata
1212
13+
use driver::{driver, session};
1314
use driver::session::Session;
15+
use metadata::csearch;
1416
use metadata::cstore;
1517
use metadata::decoder;
1618
use metadata::loader;
19+
use metadata::loader::Os;
1720

1821
use std::cell::RefCell;
1922
use std::hashmap::HashMap;
@@ -23,6 +26,7 @@ use syntax::attr;
2326
use syntax::attr::AttrMetaMethods;
2427
use syntax::codemap::{Span, DUMMY_SP};
2528
use syntax::diagnostic::SpanHandler;
29+
use syntax::ext::base::{CrateLoader, MacroCrate};
2630
use syntax::parse::token;
2731
use syntax::parse::token::IdentInterner;
2832
use syntax::crateid::CrateId;
@@ -131,37 +135,65 @@ fn visit_crate(e: &Env, c: &ast::Crate) {
131135
}
132136

133137
fn visit_view_item(e: &mut Env, i: &ast::ViewItem) {
138+
let should_load = i.attrs.iter().all(|attr| {
139+
"phase" != attr.name() ||
140+
attr.meta_item_list().map_or(false, |phases| {
141+
attr::contains_name(phases, "link")
142+
})
143+
});
144+
145+
if !should_load {
146+
return;
147+
}
148+
149+
match extract_crate_info(i) {
150+
Some(info) => {
151+
let cnum = resolve_crate(e, info.ident, info.name, info.version,
152+
@"", i.span);
153+
e.sess.cstore.add_extern_mod_stmt_cnum(info.id, cnum);
154+
}
155+
None => ()
156+
}
157+
}
158+
159+
struct CrateInfo {
160+
ident: @str,
161+
name: @str,
162+
version: @str,
163+
id: ast::NodeId,
164+
}
165+
166+
fn extract_crate_info(i: &ast::ViewItem) -> Option<CrateInfo> {
134167
match i.node {
135-
ast::ViewItemExternMod(ident, path_opt, id) => {
136-
let ident = token::ident_to_str(&ident);
137-
debug!("resolving extern mod stmt. ident: {:?} path_opt: {:?}",
138-
ident, path_opt);
139-
let (name, version) = match path_opt {
140-
Some((path_str, _)) => {
141-
let crateid: Option<CrateId> = from_str(path_str);
142-
match crateid {
143-
None => (@"", @""),
144-
Some(crateid) => {
145-
let version = match crateid.version {
146-
None => @"",
147-
Some(ref ver) => ver.to_managed(),
148-
};
149-
(crateid.name.to_managed(), version)
150-
}
151-
}
152-
}
153-
None => (ident, @""),
154-
};
155-
let cnum = resolve_crate(e,
156-
ident,
157-
name,
158-
version,
159-
@"",
160-
i.span);
161-
e.sess.cstore.add_extern_mod_stmt_cnum(id, cnum);
162-
}
163-
_ => ()
164-
}
168+
ast::ViewItemExternMod(ident, path_opt, id) => {
169+
let ident = token::ident_to_str(&ident);
170+
debug!("resolving extern mod stmt. ident: {:?} path_opt: {:?}",
171+
ident, path_opt);
172+
let (name, version) = match path_opt {
173+
Some((path_str, _)) => {
174+
let crateid: Option<CrateId> = from_str(path_str);
175+
match crateid {
176+
None => (@"", @""),
177+
Some(crateid) => {
178+
let version = match crateid.version {
179+
None => @"",
180+
Some(ref ver) => ver.to_managed(),
181+
};
182+
(crateid.name.to_managed(), version)
183+
}
184+
}
185+
}
186+
None => (ident, @""),
187+
};
188+
Some(CrateInfo {
189+
ident: ident,
190+
name: name,
191+
version: version,
192+
id: id,
193+
})
194+
}
195+
_ => None
196+
}
165197
}
166198

167199
fn visit_item(e: &Env, i: &ast::Item) {
@@ -355,3 +387,46 @@ fn resolve_crate_deps(e: &mut Env, cdata: &[u8]) -> cstore::cnum_map {
355387
}
356388
return @RefCell::new(cnum_map);
357389
}
390+
391+
pub struct Loader {
392+
priv env: Env,
393+
}
394+
395+
impl Loader {
396+
pub fn new(sess: Session) -> Loader {
397+
let os = driver::get_os(driver::host_triple()).unwrap();
398+
let os = session::sess_os_to_meta_os(os);
399+
Loader {
400+
env: Env {
401+
sess: sess,
402+
os: os,
403+
crate_cache: @RefCell::new(~[]),
404+
next_crate_num: 1,
405+
intr: token::get_ident_interner(),
406+
}
407+
}
408+
}
409+
}
410+
411+
impl CrateLoader for Loader {
412+
fn load_crate(&mut self, crate: &ast::ViewItem) -> MacroCrate {
413+
let info = extract_crate_info(crate).unwrap();
414+
let cnum = resolve_crate(&mut self.env, info.ident, info.name,
415+
info.version, @"", crate.span);
416+
let library = self.env.sess.cstore.get_used_crate_source(cnum).unwrap();
417+
MacroCrate {
418+
lib: library.dylib,
419+
cnum: cnum
420+
}
421+
}
422+
423+
fn get_exported_macros(&mut self, cnum: ast::CrateNum) -> ~[@ast::Item] {
424+
csearch::get_exported_macros(self.env.sess.cstore, cnum)
425+
}
426+
427+
fn get_registrar_symbol(&mut self, cnum: ast::CrateNum) -> Option<~str> {
428+
let cstore = self.env.sess.cstore;
429+
csearch::get_macro_registrar_fn(cstore, cnum)
430+
.map(|did| csearch::get_symbol(cstore, did))
431+
}
432+
}

src/librustc/metadata/csearch.rs

+13
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,16 @@ pub fn get_trait_of_method(cstore: @cstore::CStore,
301301
decoder::get_trait_of_method(cdata, def_id.node, tcx)
302302
}
303303

304+
pub fn get_macro_registrar_fn(cstore: @cstore::CStore,
305+
crate_num: ast::CrateNum)
306+
-> Option<ast::DefId> {
307+
let cdata = cstore.get_crate_data(crate_num);
308+
decoder::get_macro_registrar_fn(cdata)
309+
}
310+
311+
pub fn get_exported_macros(cstore: @cstore::CStore,
312+
crate_num: ast::CrateNum)
313+
-> ~[@ast::Item] {
314+
let cdata = cstore.get_crate_data(crate_num);
315+
decoder::get_exported_macros(cdata)
316+
}

0 commit comments

Comments
 (0)