diff --git a/src/libcore/macros/mod.rs b/src/libcore/macros/mod.rs index 76e58f0cc62bc..3b615a5246c29 100644 --- a/src/libcore/macros/mod.rs +++ b/src/libcore/macros/mod.rs @@ -1383,6 +1383,18 @@ pub(crate) mod builtin { /* compiler built-in */ } + /// Keeps the item it's applied to if the passed path is accessible, and removes it otherwise. + #[cfg(not(bootstrap))] + #[unstable( + feature = "cfg_accessible", + issue = "64797", + reason = "`cfg_accessible` is not fully implemented" + )] + #[rustc_builtin_macro] + pub macro cfg_accessible($item:item) { + /* compiler built-in */ + } + /// Unstable implementation detail of the `rustc` compiler, do not use. #[rustc_builtin_macro] #[stable(feature = "rust1", since = "1.0.0")] diff --git a/src/libcore/prelude/v1.rs b/src/libcore/prelude/v1.rs index 66b5a90b77b91..c91370b271992 100644 --- a/src/libcore/prelude/v1.rs +++ b/src/libcore/prelude/v1.rs @@ -67,3 +67,12 @@ pub use crate::{ pub use crate::macros::builtin::{ bench, global_allocator, test, test_case, RustcDecodable, RustcEncodable, }; + +#[cfg(not(bootstrap))] +#[unstable( + feature = "cfg_accessible", + issue = "64797", + reason = "`cfg_accessible` is not fully implemented" +)] +#[doc(no_inline)] +pub use crate::macros::builtin::cfg_accessible; diff --git a/src/librustc_builtin_macros/cfg_accessible.rs b/src/librustc_builtin_macros/cfg_accessible.rs new file mode 100644 index 0000000000000..3607a4d0d15b6 --- /dev/null +++ b/src/librustc_builtin_macros/cfg_accessible.rs @@ -0,0 +1,54 @@ +//! Implementation of the `#[cfg_accessible(path)]` attribute macro. + +use rustc_ast::ast; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier}; +use rustc_feature::AttributeTemplate; +use rustc_parse::validate_attr; +use rustc_span::symbol::sym; +use rustc_span::Span; + +crate struct Expander; + +fn validate_input<'a>(ecx: &mut ExtCtxt<'_>, mi: &'a ast::MetaItem) -> Option<&'a ast::Path> { + match mi.meta_item_list() { + None => {} + Some([]) => ecx.span_err(mi.span, "`cfg_accessible` path is not specified"), + Some([_, .., l]) => ecx.span_err(l.span(), "multiple `cfg_accessible` paths are specified"), + Some([nmi]) => match nmi.meta_item() { + None => ecx.span_err(nmi.span(), "`cfg_accessible` path cannot be a literal"), + Some(mi) => { + if !mi.is_word() { + ecx.span_err(mi.span, "`cfg_accessible` path cannot accept arguments"); + } + return Some(&mi.path); + } + }, + } + None +} + +impl MultiItemModifier for Expander { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + _span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + ) -> ExpandResult, Annotatable> { + let template = AttributeTemplate { list: Some("path"), ..Default::default() }; + let attr = &ecx.attribute(meta_item.clone()); + validate_attr::check_builtin_attribute(ecx.parse_sess, attr, sym::cfg_accessible, template); + + let path = match validate_input(ecx, meta_item) { + Some(path) => path, + None => return ExpandResult::Ready(Vec::new()), + }; + + let failure_msg = "cannot determine whether the path is accessible or not"; + match ecx.resolver.cfg_accessible(ecx.current_expansion.id, path) { + Ok(true) => ExpandResult::Ready(vec![item]), + Ok(false) => ExpandResult::Ready(Vec::new()), + Err(_) => ExpandResult::Retry(item, failure_msg.into()), + } + } +} diff --git a/src/librustc_builtin_macros/lib.rs b/src/librustc_builtin_macros/lib.rs index 9a8b0a87cb793..26a59c6b1bedb 100644 --- a/src/librustc_builtin_macros/lib.rs +++ b/src/librustc_builtin_macros/lib.rs @@ -22,6 +22,7 @@ use rustc_span::symbol::sym; mod asm; mod assert; mod cfg; +mod cfg_accessible; mod compile_error; mod concat; mod concat_idents; @@ -85,6 +86,7 @@ pub fn register_builtin_macros(resolver: &mut dyn Resolver, edition: Edition) { register_attr! { bench: test::expand_bench, + cfg_accessible: cfg_accessible::Expander, global_allocator: global_allocator::expand, test: test::expand_test, test_case: test::expand_test_case, diff --git a/src/librustc_expand/base.rs b/src/librustc_expand/base.rs index 3782746a57de4..4839651ac844b 100644 --- a/src/librustc_expand/base.rs +++ b/src/librustc_expand/base.rs @@ -897,6 +897,7 @@ pub trait Resolver { fn has_derive_copy(&self, expn_id: ExpnId) -> bool; fn add_derive_copy(&mut self, expn_id: ExpnId); + fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result; } #[derive(Clone)] diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index e11aec906693e..4e41ea82c47c9 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -339,6 +339,42 @@ impl<'a> base::Resolver for Resolver<'a> { fn add_derive_copy(&mut self, expn_id: ExpnId) { self.containers_deriving_copy.insert(expn_id); } + + // The function that implements the resolution logic of `#[cfg_accessible(path)]`. + // Returns true if the path can certainly be resolved in one of three namespaces, + // returns false if the path certainly cannot be resolved in any of the three namespaces. + // Returns `Indeterminate` if we cannot give a certain answer yet. + fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result { + let span = path.span; + let path = &Segment::from_path(path); + let parent_scope = self.invocation_parent_scopes[&expn_id]; + + let mut indeterminate = false; + for ns in [TypeNS, ValueNS, MacroNS].iter().copied() { + match self.resolve_path(path, Some(ns), &parent_scope, false, span, CrateLint::No) { + PathResult::Module(ModuleOrUniformRoot::Module(_)) => return Ok(true), + PathResult::NonModule(partial_res) if partial_res.unresolved_segments() == 0 => { + return Ok(true); + } + PathResult::Indeterminate => indeterminate = true, + // FIXME: `resolve_path` is not ready to report partially resolved paths + // correctly, so we just report an error if the path was reported as unresolved. + // This needs to be fixed for `cfg_accessible` to be useful. + PathResult::NonModule(..) | PathResult::Failed { .. } => {} + PathResult::Module(_) => panic!("unexpected path resolution"), + } + } + + if indeterminate { + return Err(Indeterminate); + } + + self.session + .struct_span_err(span, "not sure whether the path is accessible or not") + .span_note(span, "`cfg_accessible` is not fully implemented") + .emit(); + Ok(false) + } } impl<'a> Resolver<'a> { diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index c39f9f360c027..55284e73efcfa 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -181,6 +181,7 @@ symbols! { caller_location, cdylib, cfg, + cfg_accessible, cfg_attr, cfg_attr_multi, cfg_doctest, diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 784868b52e517..9950a8132555f 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -240,6 +240,7 @@ #![feature(atomic_mut_ptr)] #![feature(box_syntax)] #![feature(c_variadic)] +#![cfg_attr(not(bootstrap), feature(cfg_accessible))] #![feature(cfg_target_has_atomic)] #![feature(cfg_target_thread_local)] #![feature(char_error_internals)] diff --git a/src/libstd/prelude/v1.rs b/src/libstd/prelude/v1.rs index 7c0efe828c27a..6712f5ba5808c 100644 --- a/src/libstd/prelude/v1.rs +++ b/src/libstd/prelude/v1.rs @@ -53,6 +53,15 @@ pub use core::prelude::v1::{ PartialEq, PartialOrd, RustcDecodable, RustcEncodable, }; +#[cfg(not(bootstrap))] +#[unstable( + feature = "cfg_accessible", + issue = "64797", + reason = "`cfg_accessible` is not fully implemented" +)] +#[doc(hidden)] +pub use core::prelude::v1::cfg_accessible; + // The file so far is equivalent to src/libcore/prelude/v1.rs, // and below to src/liballoc/prelude.rs. // Those files are duplicated rather than using glob imports diff --git a/src/test/ui/conditional-compilation/cfg_accessible-input-validation.rs b/src/test/ui/conditional-compilation/cfg_accessible-input-validation.rs new file mode 100644 index 0000000000000..c51c908a4262e --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible-input-validation.rs @@ -0,0 +1,24 @@ +#![feature(cfg_accessible)] + +#[cfg_accessible] //~ ERROR malformed `cfg_accessible` attribute input +struct S1; + +#[cfg_accessible = "value"] //~ ERROR malformed `cfg_accessible` attribute input +struct S2; + +#[cfg_accessible()] //~ ERROR `cfg_accessible` path is not specified +struct S3; + +#[cfg_accessible(std, core)] //~ ERROR multiple `cfg_accessible` paths are specified +struct S4; + +#[cfg_accessible("std")] //~ ERROR `cfg_accessible` path cannot be a literal +struct S5; + +#[cfg_accessible(std = "value")] //~ ERROR `cfg_accessible` path cannot accept arguments +struct S6; + +#[cfg_accessible(std(value))] //~ ERROR `cfg_accessible` path cannot accept arguments +struct S7; + +fn main() {} diff --git a/src/test/ui/conditional-compilation/cfg_accessible-input-validation.stderr b/src/test/ui/conditional-compilation/cfg_accessible-input-validation.stderr new file mode 100644 index 0000000000000..86706c766356e --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible-input-validation.stderr @@ -0,0 +1,44 @@ +error: malformed `cfg_accessible` attribute input + --> $DIR/cfg_accessible-input-validation.rs:3:1 + | +LL | #[cfg_accessible] + | ^^^^^^^^^^^^^^^^^ help: must be of the form: `#[cfg_accessible(path)]` + +error: malformed `cfg_accessible` attribute input + --> $DIR/cfg_accessible-input-validation.rs:6:1 + | +LL | #[cfg_accessible = "value"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[cfg_accessible(path)]` + +error: `cfg_accessible` path is not specified + --> $DIR/cfg_accessible-input-validation.rs:9:1 + | +LL | #[cfg_accessible()] + | ^^^^^^^^^^^^^^^^^^^ + +error: multiple `cfg_accessible` paths are specified + --> $DIR/cfg_accessible-input-validation.rs:12:23 + | +LL | #[cfg_accessible(std, core)] + | ^^^^ + +error: `cfg_accessible` path cannot be a literal + --> $DIR/cfg_accessible-input-validation.rs:15:18 + | +LL | #[cfg_accessible("std")] + | ^^^^^ + +error: `cfg_accessible` path cannot accept arguments + --> $DIR/cfg_accessible-input-validation.rs:18:18 + | +LL | #[cfg_accessible(std = "value")] + | ^^^^^^^^^^^^^ + +error: `cfg_accessible` path cannot accept arguments + --> $DIR/cfg_accessible-input-validation.rs:21:18 + | +LL | #[cfg_accessible(std(value))] + | ^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/test/ui/conditional-compilation/cfg_accessible-stuck.rs b/src/test/ui/conditional-compilation/cfg_accessible-stuck.rs new file mode 100644 index 0000000000000..8bc93fa324378 --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible-stuck.rs @@ -0,0 +1,9 @@ +#![feature(cfg_accessible)] + +#[cfg_accessible(Z)] //~ ERROR cannot determine whether the path is accessible or not +struct S; + +#[cfg_accessible(S)] //~ ERROR cannot determine whether the path is accessible or not +struct Z; + +fn main() {} diff --git a/src/test/ui/conditional-compilation/cfg_accessible-stuck.stderr b/src/test/ui/conditional-compilation/cfg_accessible-stuck.stderr new file mode 100644 index 0000000000000..9641441a819b0 --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible-stuck.stderr @@ -0,0 +1,14 @@ +error: cannot determine whether the path is accessible or not + --> $DIR/cfg_accessible-stuck.rs:6:1 + | +LL | #[cfg_accessible(S)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: cannot determine whether the path is accessible or not + --> $DIR/cfg_accessible-stuck.rs:3:1 + | +LL | #[cfg_accessible(Z)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/conditional-compilation/cfg_accessible-unstable.rs b/src/test/ui/conditional-compilation/cfg_accessible-unstable.rs new file mode 100644 index 0000000000000..e9247e67a2a26 --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible-unstable.rs @@ -0,0 +1,2 @@ +#[cfg_accessible(std)] //~ ERROR use of unstable library feature 'cfg_accessible' +fn main() {} diff --git a/src/test/ui/conditional-compilation/cfg_accessible-unstable.stderr b/src/test/ui/conditional-compilation/cfg_accessible-unstable.stderr new file mode 100644 index 0000000000000..2f55b9559c78f --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible-unstable.stderr @@ -0,0 +1,12 @@ +error[E0658]: use of unstable library feature 'cfg_accessible': `cfg_accessible` is not fully implemented + --> $DIR/cfg_accessible-unstable.rs:1:3 + | +LL | #[cfg_accessible(std)] + | ^^^^^^^^^^^^^^ + | + = note: see issue #64797 for more information + = help: add `#![feature(cfg_accessible)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/conditional-compilation/cfg_accessible.rs b/src/test/ui/conditional-compilation/cfg_accessible.rs new file mode 100644 index 0000000000000..07b0be5b1ae26 --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible.rs @@ -0,0 +1,43 @@ +#![feature(cfg_accessible)] + +mod m { + pub struct ExistingPublic; + struct ExistingPrivate; +} + +#[cfg_accessible(m::ExistingPublic)] +struct ExistingPublic; + +// FIXME: Not implemented yet. +#[cfg_accessible(m::ExistingPrivate)] //~ ERROR not sure whether the path is accessible or not +struct ExistingPrivate; + +// FIXME: Not implemented yet. +#[cfg_accessible(m::NonExistent)] //~ ERROR not sure whether the path is accessible or not +struct ExistingPrivate; + +#[cfg_accessible(n::AccessibleExpanded)] // OK, `cfg_accessible` can wait and retry. +struct AccessibleExpanded; + +macro_rules! generate_accessible_expanded { + () => { + mod n { + pub struct AccessibleExpanded; + } + }; +} + +generate_accessible_expanded!(); + +struct S { + field: u8, +} + +// FIXME: Not implemented yet. +#[cfg_accessible(S::field)] //~ ERROR not sure whether the path is accessible or not +struct Field; + +fn main() { + ExistingPublic; + AccessibleExpanded; +} diff --git a/src/test/ui/conditional-compilation/cfg_accessible.stderr b/src/test/ui/conditional-compilation/cfg_accessible.stderr new file mode 100644 index 0000000000000..167765cd66ee6 --- /dev/null +++ b/src/test/ui/conditional-compilation/cfg_accessible.stderr @@ -0,0 +1,38 @@ +error: not sure whether the path is accessible or not + --> $DIR/cfg_accessible.rs:12:18 + | +LL | #[cfg_accessible(m::ExistingPrivate)] + | ^^^^^^^^^^^^^^^^^^ + | +note: `cfg_accessible` is not fully implemented + --> $DIR/cfg_accessible.rs:12:18 + | +LL | #[cfg_accessible(m::ExistingPrivate)] + | ^^^^^^^^^^^^^^^^^^ + +error: not sure whether the path is accessible or not + --> $DIR/cfg_accessible.rs:16:18 + | +LL | #[cfg_accessible(m::NonExistent)] + | ^^^^^^^^^^^^^^ + | +note: `cfg_accessible` is not fully implemented + --> $DIR/cfg_accessible.rs:16:18 + | +LL | #[cfg_accessible(m::NonExistent)] + | ^^^^^^^^^^^^^^ + +error: not sure whether the path is accessible or not + --> $DIR/cfg_accessible.rs:37:18 + | +LL | #[cfg_accessible(S::field)] + | ^^^^^^^^ + | +note: `cfg_accessible` is not fully implemented + --> $DIR/cfg_accessible.rs:37:18 + | +LL | #[cfg_accessible(S::field)] + | ^^^^^^^^ + +error: aborting due to 3 previous errors +