From e520bb1b2f1667f17c3503af71273921c4fc9989 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sun, 3 Aug 2014 14:25:30 -0700 Subject: [PATCH 1/3] Add a cfg_attr syntax extension This extends cfg-gating to attributes. ```rust #[cfg_attr(, )] ``` will expand to ```rust #[] ``` if the `` matches the current cfg environment, and nothing if it does not. The grammar for the cfg pattern has a simple recursive structure: * `value` and `key = "value"` are cfg patterns, * `not()` is a cfg pattern and matches if `` does not. * `all(, ...)` is a cfg pattern and matches if all of the ``s do. * `any(, ...)` is a cfg pattern and matches if any of the ``s do. Examples: ```rust // only derive Show for assert_eq! in tests #[cfg_attr(test, deriving(Show))] struct Foo { ... } // only derive Show for assert_eq! in tests and debug builds #[cfg_attr(any(test, not(ndebug)), deriving(Show))] struct Foo { ... } // ignore a test in certain cases #[test] #[cfg_attr(all(not(target_os = "linux"), target_endian = "big"), ignore)] fn test_broken_thing() { ... } // Avoid duplication when fixing staging issues in rustc #[cfg_attr(not(stage0), lang="iter")] pub trait Iterator { ... } ``` --- src/libsyntax/ext/base.rs | 2 ++ src/libsyntax/ext/cfg_attr.rs | 59 +++++++++++++++++++++++++++++++++++ src/libsyntax/lib.rs | 1 + src/test/run-pass/cfg_attr.rs | 55 ++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/libsyntax/ext/cfg_attr.rs create mode 100644 src/test/run-pass/cfg_attr.rs diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index b35a945675761..79dc623f5074f 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -439,6 +439,8 @@ fn initial_syntax_expander_table() -> SyntaxEnv { syntax_expanders.insert(intern("cfg"), builtin_normal_expander( ext::cfg::expand_cfg)); + syntax_expanders.insert(intern("cfg_attr"), + ItemModifier(ext::cfg_attr::expand)); syntax_expanders.insert(intern("trace_macros"), builtin_normal_expander( ext::trace_macros::expand_trace_macros)); diff --git a/src/libsyntax/ext/cfg_attr.rs b/src/libsyntax/ext/cfg_attr.rs new file mode 100644 index 0000000000000..5df94ac526d08 --- /dev/null +++ b/src/libsyntax/ext/cfg_attr.rs @@ -0,0 +1,59 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::gc::{Gc, GC}; + +use ast; +use attr; +use codemap::Span; +use ext::base::ExtCtxt; +use ext::build::AstBuilder; + +pub fn expand(cx: &mut ExtCtxt, sp: Span, mi: Gc, it: Gc) + -> Gc { + let (cfg, attr) = match mi.node { + ast::MetaList(_, ref mis) if mis.len() == 2 => (mis[0], mis[1]), + _ => { + cx.span_err(sp, "expected `#[cfg_attr(, )]`"); + return it; + } + }; + + let mut out = (*it).clone(); + if cfg_matches(cx, cfg) { + out.attrs.push(cx.attribute(attr.span, attr)); + } + + box(GC) out +} + +fn cfg_matches(cx: &mut ExtCtxt, cfg: Gc) -> bool { + match cfg.node { + ast::MetaList(ref pred, ref mis) if pred.get() == "any" => + mis.iter().any(|mi| cfg_matches(cx, *mi)), + ast::MetaList(ref pred, ref mis) if pred.get() == "all" => + mis.iter().all(|mi| cfg_matches(cx, *mi)), + ast::MetaList(ref pred, ref mis) if pred.get() == "not" => { + if mis.len() != 1 { + cx.span_err(cfg.span, format!("expected 1 value, got {}", + mis.len()).as_slice()); + return false; + } + !cfg_matches(cx, mis[0]) + } + ast::MetaList(ref pred, _) => { + cx.span_err(cfg.span, + format!("invalid predicate `{}`", pred).as_slice()); + false + }, + ast::MetaWord(_) | ast::MetaNameValue(..) => + attr::contains(cx.cfg.as_slice(), cfg), + } +} diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 153b3cc90d601..7a504d22c1e9e 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -83,6 +83,7 @@ pub mod ext { pub mod build; pub mod bytes; pub mod cfg; + pub mod cfg_attr; pub mod concat; pub mod concat_idents; pub mod deriving; diff --git a/src/test/run-pass/cfg_attr.rs b/src/test/run-pass/cfg_attr.rs new file mode 100644 index 0000000000000..4f579cd75fee3 --- /dev/null +++ b/src/test/run-pass/cfg_attr.rs @@ -0,0 +1,55 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags:--cfg set1 --cfg set2 +#![allow(dead_code)] +use std::fmt::Show; + +struct NotShowable; + +#[cfg_attr(set1, deriving(Show))] +struct Set1; + +#[cfg_attr(notset, deriving(Show))] +struct Notset(NotShowable); + +#[cfg_attr(not(notset), deriving(Show))] +struct NotNotset; + +#[cfg_attr(not(set1), deriving(Show))] +struct NotSet1(NotShowable); + +#[cfg_attr(all(set1, set2), deriving(Show))] +struct AllSet1Set2; + +#[cfg_attr(all(set1, notset), deriving(Show))] +struct AllSet1Notset(NotShowable); + +#[cfg_attr(any(set1, notset), deriving(Show))] +struct AnySet1Notset; + +#[cfg_attr(any(notset, notset2), deriving(Show))] +struct AnyNotsetNotset2(NotShowable); + +#[cfg_attr(all(not(notset), any(set1, notset)), deriving(Show))] +struct Complex; + +#[cfg_attr(any(notset, not(any(set1, notset))), deriving(Show))] +struct ComplexNot(NotShowable); + +fn is_show() {} + +fn main() { + is_show::(); + is_show::(); + is_show::(); + is_show::(); + is_show::(); +} From 65cca7c8b14e9c5673af4921a57c2ccbac3fc2cb Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sun, 3 Aug 2014 17:41:58 -0700 Subject: [PATCH 2/3] Deprecate `#[ignore(cfg(...))]` Replace `#[ignore(cfg(a, b))]` with `#[cfg_attr(all(a, b), ignore)]` --- src/doc/guide-testing.md | 4 ++-- src/libnative/io/file_unix.rs | 2 +- src/libnum/complex.rs | 2 +- src/librustuv/net.rs | 2 +- src/libserialize/json.rs | 6 +++--- src/libstd/dynamic_lib.rs | 3 +-- src/libstd/io/net/pipe.rs | 2 +- src/libstd/io/net/tcp.rs | 2 +- src/libstd/io/net/udp.rs | 2 +- src/libstd/num/f32.rs | 2 +- src/libstd/num/f64.rs | 2 +- src/libsync/deque.rs | 2 +- src/libsyntax/test.rs | 6 ++++++ src/libtest/lib.rs | 3 +-- src/libtime/lib.rs | 2 +- src/test/run-pass/cfg_attr.rs | 4 ++++ src/test/run-pass/tcp-connect-timeouts.rs | 2 +- 17 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/doc/guide-testing.md b/src/doc/guide-testing.md index 6fb198fbd2ba4..07813855d9b9b 100644 --- a/src/doc/guide-testing.md +++ b/src/doc/guide-testing.md @@ -72,8 +72,8 @@ is not used. Tests that should not be run can be annotated with the `ignore` attribute. The existence of these tests will be noted in the test runner output, but the test will not be run. Tests can also be ignored -by configuration so, for example, to ignore a test on windows you can -write `#[ignore(cfg(target_os = "win32"))]`. +by configuration using the `cfg_attr` attribute so, for example, to ignore a +test on windows you can write `#[cfg_attr(windows, ignore)]`. Tests that are intended to fail can be annotated with the `should_fail` attribute. The test will be run, and if it causes its diff --git a/src/libnative/io/file_unix.rs b/src/libnative/io/file_unix.rs index 3c49e1c40d63c..97d48df600ee4 100644 --- a/src/libnative/io/file_unix.rs +++ b/src/libnative/io/file_unix.rs @@ -506,7 +506,7 @@ mod tests { use std::os; use std::rt::rtio::{RtioFileStream, SeekSet}; - #[ignore(cfg(target_os = "freebsd"))] // hmm, maybe pipes have a tiny buffer + #[cfg_attr(target_os = "freebsd", ignore)] // hmm, maybe pipes have a tiny buffer #[test] fn test_file_desc() { // Run this test with some pipes so we don't have to mess around with diff --git a/src/libnum/complex.rs b/src/libnum/complex.rs index 24c99a38bd91a..0e70527cdca79 100644 --- a/src/libnum/complex.rs +++ b/src/libnum/complex.rs @@ -219,7 +219,7 @@ mod test { } #[test] - #[ignore(cfg(target_arch = "x86"))] + #[cfg_attr(target_arch = "x86", ignore)] // FIXME #7158: (maybe?) currently failing on x86. fn test_norm() { fn test(c: Complex64, ns: f64) { diff --git a/src/librustuv/net.rs b/src/librustuv/net.rs index d572d8ce58a53..3531d162db081 100644 --- a/src/librustuv/net.rs +++ b/src/librustuv/net.rs @@ -1085,7 +1085,7 @@ mod test { } #[test] - #[ignore(cfg(windows))] // FIXME(#10102) server never sees second packet + #[cfg_attr(windows, ignore)] // FIXME(#10102) server never sees second packet fn test_udp_twice() { let server_addr = ::next_test_ip4(); let client_addr = ::next_test_ip4(); diff --git a/src/libserialize/json.rs b/src/libserialize/json.rs index 14274ef9f9b04..7cb448cf15c3a 100644 --- a/src/libserialize/json.rs +++ b/src/libserialize/json.rs @@ -3352,7 +3352,7 @@ mod tests { } } #[test] - #[ignore(cfg(target_word_size = "32"))] // FIXME(#14064) + #[cfg_attr(target_word_size = "32", ignore)] // FIXME(#14064) fn test_streaming_parser() { assert_stream_equal( r#"{ "foo":"bar", "array" : [0, 1, 2, 3, 4, 5], "idents":[null,true,false]}"#, @@ -3388,7 +3388,7 @@ mod tests { } #[test] - #[ignore(cfg(target_word_size = "32"))] // FIXME(#14064) + #[cfg_attr(target_word_size = "32", ignore)] // FIXME(#14064) fn test_read_object_streaming() { assert_eq!(last_event("{ "), Error(SyntaxError(EOFWhileParsingObject, 1, 3))); assert_eq!(last_event("{1"), Error(SyntaxError(KeyMustBeAString, 1, 2))); @@ -3461,7 +3461,7 @@ mod tests { ); } #[test] - #[ignore(cfg(target_word_size = "32"))] // FIXME(#14064) + #[cfg_attr(target_word_size = "32", ignore)] // FIXME(#14064) fn test_read_list_streaming() { assert_stream_equal( "[]", diff --git a/src/libstd/dynamic_lib.rs b/src/libstd/dynamic_lib.rs index e8d570f30e614..a88448f47e0c6 100644 --- a/src/libstd/dynamic_lib.rs +++ b/src/libstd/dynamic_lib.rs @@ -162,8 +162,7 @@ mod test { use mem; #[test] - #[ignore(cfg(windows))] // FIXME #8818 - #[ignore(cfg(target_os="android"))] // FIXME(#10379) + #[cfg_attr(any(windows, target_os = "android"), ignore)] // FIXME #8818, #10379 fn test_loading_cosine() { // The math library does not need to be loaded since it is already // statically linked in diff --git a/src/libstd/io/net/pipe.rs b/src/libstd/io/net/pipe.rs index bdd58b07d8615..9faa018237245 100644 --- a/src/libstd/io/net/pipe.rs +++ b/src/libstd/io/net/pipe.rs @@ -320,7 +320,7 @@ mod tests { }, proc(_client) { // drop the client }) - } #[ignore(cfg(windows))]) // FIXME(#12516) + } #[cfg_attr(windows, ignore)]) // FIXME(#12516) iotest!(fn write_begone() { smalltest(proc(mut server) { diff --git a/src/libstd/io/net/tcp.rs b/src/libstd/io/net/tcp.rs index d787f0e98541c..65d8f1c9c7764 100644 --- a/src/libstd/io/net/tcp.rs +++ b/src/libstd/io/net/tcp.rs @@ -533,7 +533,7 @@ mod test { Ok(..) => fail!(), Err(e) => assert_eq!(e.kind, PermissionDenied), } - } #[ignore(cfg(windows))] #[ignore(cfg(target_os = "android"))]) + } #[cfg_attr(any(windows, target_os = "android"), ignore)]) iotest!(fn connect_error() { match TcpStream::connect("0.0.0.0", 1) { diff --git a/src/libstd/io/net/udp.rs b/src/libstd/io/net/udp.rs index 6689615f01b22..9718ceb118555 100644 --- a/src/libstd/io/net/udp.rs +++ b/src/libstd/io/net/udp.rs @@ -273,7 +273,7 @@ mod test { Ok(..) => fail!(), Err(e) => assert_eq!(e.kind, PermissionDenied), } - } #[ignore(cfg(windows))] #[ignore(cfg(target_os = "android"))]) + } #[cfg_attr(any(windows, target_os = "android"), ignore)]) iotest!(fn socket_smoke_test_ip4() { let server_ip = next_test_ip4(); diff --git a/src/libstd/num/f32.rs b/src/libstd/num/f32.rs index d91f5579c0d7a..b2a9f1b7b20d3 100644 --- a/src/libstd/num/f32.rs +++ b/src/libstd/num/f32.rs @@ -766,7 +766,7 @@ mod tests { assert_eq!((-0f32).frexp(), (-0f32, 0)); } - #[test] #[ignore(cfg(windows))] // FIXME #8755 + #[test] #[cfg_attr(windows, ignore)] // FIXME #8755 fn test_frexp_nowin() { let inf: f32 = Float::infinity(); let neg_inf: f32 = Float::neg_infinity(); diff --git a/src/libstd/num/f64.rs b/src/libstd/num/f64.rs index d8d118a6721d9..6fe9fcad2aad7 100644 --- a/src/libstd/num/f64.rs +++ b/src/libstd/num/f64.rs @@ -768,7 +768,7 @@ mod tests { assert_eq!((-0f64).frexp(), (-0f64, 0)); } - #[test] #[ignore(cfg(windows))] // FIXME #8755 + #[test] #[cfg_attr(windows, ignore)] // FIXME #8755 fn test_frexp_nowin() { let inf: f64 = Float::infinity(); let neg_inf: f64 = Float::neg_infinity(); diff --git a/src/libsync/deque.rs b/src/libsync/deque.rs index 6204b54fba4be..15c0c14b28a86 100644 --- a/src/libsync/deque.rs +++ b/src/libsync/deque.rs @@ -600,7 +600,7 @@ mod tests { } #[test] - #[ignore(cfg(windows))] // apparently windows scheduling is weird? + #[cfg_attr(windows, ignore)] // apparently windows scheduling is weird? fn no_starvation() { static AMT: int = 10000; static NTHREADS: int = 4; diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index f0e697127149f..b6e3023d2ae3a 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -339,6 +339,12 @@ fn is_ignored(cx: &TestCtxt, i: &ast::Item) -> bool { // check ignore(cfg(foo, bar)) attr.check_name("ignore") && match attr.meta_item_list() { Some(ref cfgs) => { + if cfgs.iter().any(|cfg| cfg.check_name("cfg")) { + cx.sess.span_warn(attr.span, + "The use of cfg filters in #[ignore] is \ + deprecated. Use #[cfg_attr(, \ + ignore)] instead."); + } attr::test_cfg(cx.config.as_slice(), cfgs.iter()) } None => true diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 43a1899f45e08..04489d780278d 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -356,8 +356,7 @@ Test Attributes: #[ignore] - When applied to a function which is already attributed as a test, then the test runner will ignore these tests during normal test runs. Running with --ignored will run these - tests. This may also be written as #[ignore(cfg(...))] to - ignore the test on certain configurations.", + tests.", usage = getopts::usage(message.as_slice(), optgroups().as_slice())); } diff --git a/src/libtime/lib.rs b/src/libtime/lib.rs index abf6fc5f2558c..9cec71104d403 100644 --- a/src/libtime/lib.rs +++ b/src/libtime/lib.rs @@ -1564,7 +1564,7 @@ mod tests { } #[test] - #[ignore(cfg(target_os = "android"))] // FIXME #10958 + #[cfg_attr(target_os = "android", ignore)] // FIXME #10958 fn run_tests() { // The tests race on tzset. So instead of having many independent // tests, we will just call the functions now. diff --git a/src/test/run-pass/cfg_attr.rs b/src/test/run-pass/cfg_attr.rs index 4f579cd75fee3..15d3f4a04d57a 100644 --- a/src/test/run-pass/cfg_attr.rs +++ b/src/test/run-pass/cfg_attr.rs @@ -44,6 +44,9 @@ struct Complex; #[cfg_attr(any(notset, not(any(set1, notset))), deriving(Show))] struct ComplexNot(NotShowable); +#[cfg_attr(any(target_endian = "little", target_endian = "big"), deriving(Show))] +struct KeyValue; + fn is_show() {} fn main() { @@ -52,4 +55,5 @@ fn main() { is_show::(); is_show::(); is_show::(); + is_show::(); } diff --git a/src/test/run-pass/tcp-connect-timeouts.rs b/src/test/run-pass/tcp-connect-timeouts.rs index c1d93033ab6fe..00906004dbfb0 100644 --- a/src/test/run-pass/tcp-connect-timeouts.rs +++ b/src/test/run-pass/tcp-connect-timeouts.rs @@ -80,7 +80,7 @@ iotest!(fn eventual_timeout() { } } fail!("never timed out!"); -} #[ignore(cfg(target_os = "freebsd"))]) +} #[cfg_attr(target_os = "freebsd", ignore)]) iotest!(fn timeout_success() { let addr = next_test_ip4(); From dcdbdc10036b444ef39c329e9440d4acc6975fda Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Wed, 24 Sep 2014 00:35:42 -0700 Subject: [PATCH 3/3] Fix rebase fallout --- src/libsyntax/ext/base.rs | 2 +- src/libsyntax/ext/cfg_attr.rs | 22 ++++++++++------------ src/libsyntax/test.rs | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 79dc623f5074f..8bf13e20fedf7 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -440,7 +440,7 @@ fn initial_syntax_expander_table() -> SyntaxEnv { builtin_normal_expander( ext::cfg::expand_cfg)); syntax_expanders.insert(intern("cfg_attr"), - ItemModifier(ext::cfg_attr::expand)); + Modifier(box ext::cfg_attr::expand)); syntax_expanders.insert(intern("trace_macros"), builtin_normal_expander( ext::trace_macros::expand_trace_macros)); diff --git a/src/libsyntax/ext/cfg_attr.rs b/src/libsyntax/ext/cfg_attr.rs index 5df94ac526d08..ad02b50f248b4 100644 --- a/src/libsyntax/ext/cfg_attr.rs +++ b/src/libsyntax/ext/cfg_attr.rs @@ -8,18 +8,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::gc::{Gc, GC}; - use ast; use attr; use codemap::Span; use ext::base::ExtCtxt; use ext::build::AstBuilder; +use ptr::P; -pub fn expand(cx: &mut ExtCtxt, sp: Span, mi: Gc, it: Gc) - -> Gc { +pub fn expand(cx: &mut ExtCtxt, sp: Span, mi: &ast::MetaItem, it: P) -> P { let (cfg, attr) = match mi.node { - ast::MetaList(_, ref mis) if mis.len() == 2 => (mis[0], mis[1]), + ast::MetaList(_, ref mis) if mis.len() == 2 => (&mis[0], &mis[1]), _ => { cx.span_err(sp, "expected `#[cfg_attr(, )]`"); return it; @@ -27,26 +25,26 @@ pub fn expand(cx: &mut ExtCtxt, sp: Span, mi: Gc, it: Gc) -> bool { +fn cfg_matches(cx: &mut ExtCtxt, cfg: &ast::MetaItem) -> bool { match cfg.node { ast::MetaList(ref pred, ref mis) if pred.get() == "any" => - mis.iter().any(|mi| cfg_matches(cx, *mi)), + mis.iter().any(|mi| cfg_matches(cx, &**mi)), ast::MetaList(ref pred, ref mis) if pred.get() == "all" => - mis.iter().all(|mi| cfg_matches(cx, *mi)), + mis.iter().all(|mi| cfg_matches(cx, &**mi)), ast::MetaList(ref pred, ref mis) if pred.get() == "not" => { if mis.len() != 1 { cx.span_err(cfg.span, format!("expected 1 value, got {}", mis.len()).as_slice()); return false; } - !cfg_matches(cx, mis[0]) + !cfg_matches(cx, &*mis[0]) } ast::MetaList(ref pred, _) => { cx.span_err(cfg.span, diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index b6e3023d2ae3a..091b0ce8ed949 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -340,7 +340,7 @@ fn is_ignored(cx: &TestCtxt, i: &ast::Item) -> bool { attr.check_name("ignore") && match attr.meta_item_list() { Some(ref cfgs) => { if cfgs.iter().any(|cfg| cfg.check_name("cfg")) { - cx.sess.span_warn(attr.span, + cx.span_diagnostic.span_warn(attr.span, "The use of cfg filters in #[ignore] is \ deprecated. Use #[cfg_attr(, \ ignore)] instead.");