Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup macro expansion and improve diagnostics #33766

Merged
merged 11 commits into from
May 26, 2016
594 changes: 183 additions & 411 deletions src/libsyntax/ext/expand.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ use ast;
use ext::mtwt;
use ext::build::AstBuilder;
use attr;
use attr::{AttrMetaMethods, WithAttrs};
use attr::{AttrMetaMethods, WithAttrs, ThinAttributesExt};
use codemap;
use codemap::{Span, Spanned, ExpnInfo, NameAndSpan, MacroBang, MacroAttribute};
use ext::base::*;
@@ -35,59 +35,55 @@ use std_inject;
use std::collections::HashSet;
use std::env;

// this function is called to detect use of feature-gated or invalid attributes
// on macro invoations since they will not be detected after macro expansion
fn check_attributes(attrs: &[ast::Attribute], fld: &MacroExpander) {
for attr in attrs.iter() {
feature_gate::check_attribute(&attr, &fld.cx.parse_sess.span_diagnostic,
&fld.cx.parse_sess.codemap(),
&fld.cx.ecfg.features.unwrap());
}
// A trait for AST nodes and AST node lists into which macro invocations may expand.
trait MacroGenerable: Sized {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you comment on what this trait is for, what should implement it, etc.

// Expand the given MacResult using its appropriate `make_*` method.
fn make_with<'a>(result: Box<MacResult + 'a>) -> Option<Self>;

// Fold this node or list of nodes using the given folder.
fn fold_with<F: Folder>(self, folder: &mut F) -> Self;

// Return a placeholder expansion to allow compilation to continue after an erroring expansion.
fn dummy(span: Span) -> Self;

// The user-friendly name of the node type (e.g. "expression", "item", etc.) for diagnostics.
fn kind_name() -> &'static str;
}

macro_rules! impl_macro_generable {
($($ty:ty: $kind_name:expr, .$make:ident, $(.$fold:ident)* $(lift .$fold_elt:ident)*,
|$span:ident| $dummy:expr;)*) => { $(
impl MacroGenerable for $ty {
fn kind_name() -> &'static str { $kind_name }
fn make_with<'a>(result: Box<MacResult + 'a>) -> Option<Self> { result.$make() }
fn fold_with<F: Folder>(self, folder: &mut F) -> Self {
$( folder.$fold(self) )*
$( self.into_iter().flat_map(|item| folder. $fold_elt (item)).collect() )*
}
fn dummy($span: Span) -> Self { $dummy }
}
)* }
}

impl_macro_generable! {
P<ast::Expr>: "expression", .make_expr, .fold_expr, |span| DummyResult::raw_expr(span);
P<ast::Pat>: "pattern", .make_pat, .fold_pat, |span| P(DummyResult::raw_pat(span));
P<ast::Ty>: "type", .make_ty, .fold_ty, |span| DummyResult::raw_ty(span);
SmallVector<ast::ImplItem>:
"impl item", .make_impl_items, lift .fold_impl_item, |_span| SmallVector::zero();
SmallVector<P<ast::Item>>:
"item", .make_items, lift .fold_item, |_span| SmallVector::zero();
SmallVector<ast::Stmt>:
"statement", .make_stmts, lift .fold_stmt, |_span| SmallVector::zero();
}

pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
let expr_span = e.span;
return e.and_then(|ast::Expr {id, node, span, attrs}| match node {

// expr_mac should really be expr_ext or something; it's the
// entry-point for all syntax extensions.
ast::ExprKind::Mac(mac) => {
if let Some(ref attrs) = attrs {
check_attributes(attrs, fld);
}

// Assert that we drop any macro attributes on the floor here
drop(attrs);

let expanded_expr = match expand_mac_invoc(mac, span,
|r| r.make_expr(),
mark_expr, fld) {
Some(expr) => expr,
None => {
return DummyResult::raw_expr(span);
}
};

// Keep going, outside-in.
let fully_expanded = fld.fold_expr(expanded_expr);
fld.cx.bt_pop();

fully_expanded
}

ast::ExprKind::InPlace(placer, value_expr) => {
// Ensure feature-gate is enabled
if !fld.cx.ecfg.features.unwrap().placement_in_syntax {
feature_gate::emit_feature_err(
&fld.cx.parse_sess.span_diagnostic, "placement_in_syntax", expr_span,
feature_gate::GateIssue::Language, feature_gate::EXPLAIN_PLACEMENT_IN
);
}

let placer = fld.fold_expr(placer);
let value_expr = fld.fold_expr(value_expr);
fld.cx.expr(span, ast::ExprKind::InPlace(placer, value_expr))
.with_attrs(fold_thin_attrs(attrs, fld))
expand_mac_invoc(mac, None, attrs.into_attr_vec(), span, fld)
}

ast::ExprKind::While(cond, body, opt_ident) => {
@@ -182,93 +178,154 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
});
}

/// Expand a (not-ident-style) macro invocation. Returns the result
/// of expansion and the mark which must be applied to the result.
/// Our current interface doesn't allow us to apply the mark to the
/// result until after calling make_expr, make_items, etc.
fn expand_mac_invoc<T, F, G>(mac: ast::Mac,
span: codemap::Span,
parse_thunk: F,
mark_thunk: G,
fld: &mut MacroExpander)
-> Option<T> where
F: for<'a> FnOnce(Box<MacResult+'a>) -> Option<T>,
G: FnOnce(T, Mrk) -> T,
/// Expand a macro invocation. Returns the result of expansion.
fn expand_mac_invoc<T>(mac: ast::Mac, ident: Option<Ident>, attrs: Vec<ast::Attribute>, span: Span,
fld: &mut MacroExpander) -> T
where T: MacroGenerable,
{
// it would almost certainly be cleaner to pass the whole
// macro invocation in, rather than pulling it apart and
// marking the tts and the ctxt separately. This also goes
// for the other three macro invocation chunks of code
// in this file.

let Mac_ { path: pth, tts, .. } = mac.node;
if pth.segments.len() > 1 {
fld.cx.span_err(pth.span,
"expected macro name without module \
separators");
// let compilation continue
return None;
}
let extname = pth.segments[0].identifier.name;
match fld.cx.syntax_env.find(extname) {
None => {
let mut err = fld.cx.struct_span_err(
pth.span,
&format!("macro undefined: '{}!'",
&extname));
// It would almost certainly be cleaner to pass the whole macro invocation in,
// rather than pulling it apart and marking the tts and the ctxt separately.
let Mac_ { path, tts, .. } = mac.node;
let mark = fresh_mark();

fn mac_result<'a>(path: &ast::Path, ident: Option<Ident>, tts: Vec<TokenTree>, mark: Mrk,
attrs: Vec<ast::Attribute>, call_site: Span, fld: &'a mut MacroExpander)
-> Option<Box<MacResult + 'a>> {
// Detect use of feature-gated or invalid attributes on macro invoations
// since they will not be detected after macro expansion.
for attr in attrs.iter() {
feature_gate::check_attribute(&attr, &fld.cx.parse_sess.span_diagnostic,
&fld.cx.parse_sess.codemap(),
&fld.cx.ecfg.features.unwrap());
}

if path.segments.len() > 1 {
fld.cx.span_err(path.span, "expected macro name without module separators");
return None;
}

let extname = path.segments[0].identifier.name;
let extension = if let Some(extension) = fld.cx.syntax_env.find(extname) {
extension
} else {
let mut err = fld.cx.struct_span_err(path.span,
&format!("macro undefined: '{}!'", &extname));
fld.cx.suggest_macro_name(&extname.as_str(), &mut err);
err.emit();
return None;
};

// let compilation continue
None
}
Some(rc) => match *rc {
let ident = ident.unwrap_or(keywords::Invalid.ident());
match *extension {
NormalTT(ref expandfun, exp_span, allow_internal_unstable) => {
if ident.name != keywords::Invalid.name() {
let msg =
format!("macro {}! expects no ident argument, given '{}'", extname, ident);
fld.cx.span_err(path.span, &msg);
return None;
}

fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
format: MacroBang(extname),
span: exp_span,
allow_internal_unstable: allow_internal_unstable,
},
});
let fm = fresh_mark();
let marked_before = mark_tts(&tts[..], fm);
call_site: call_site,
callee: NameAndSpan {
format: MacroBang(extname),
span: exp_span,
allow_internal_unstable: allow_internal_unstable,
},
});

// The span that we pass to the expanders we want to
// be the root of the call stack. That's the most
// relevant span and it's the actual invocation of
// the macro.
let mac_span = fld.cx.original_span();

let opt_parsed = {
let expanded = expandfun.expand(fld.cx,
mac_span,
&marked_before[..]);
parse_thunk(expanded)
let marked_tts = mark_tts(&tts[..], mark);
Some(expandfun.expand(fld.cx, mac_span, &marked_tts))
}

IdentTT(ref expander, tt_span, allow_internal_unstable) => {
if ident.name == keywords::Invalid.name() {
fld.cx.span_err(path.span,
&format!("macro {}! expects an ident argument", extname));
return None;
};
let parsed = match opt_parsed {
Some(e) => e,
None => {
fld.cx.span_err(
pth.span,
&format!("non-expression macro in expression position: {}",
extname
));
return None;

fld.cx.bt_push(ExpnInfo {
call_site: call_site,
callee: NameAndSpan {
format: MacroBang(extname),
span: tt_span,
allow_internal_unstable: allow_internal_unstable,
}
});

let marked_tts = mark_tts(&tts, mark);
Some(expander.expand(fld.cx, call_site, ident, marked_tts))
}

MacroRulesTT => {
if ident.name == keywords::Invalid.name() {
fld.cx.span_err(path.span,
&format!("macro {}! expects an ident argument", extname));
return None;
};
Some(mark_thunk(parsed,fm))

fld.cx.bt_push(ExpnInfo {
call_site: call_site,
callee: NameAndSpan {
format: MacroBang(extname),
span: None,
// `macro_rules!` doesn't directly allow unstable
// (this is orthogonal to whether the macro it creates allows it)
allow_internal_unstable: false,
}
});

// DON'T mark before expansion.
fld.cx.insert_macro(ast::MacroDef {
ident: ident,
id: ast::DUMMY_NODE_ID,
span: call_site,
imported_from: None,
use_locally: true,
body: tts,
export: attr::contains_name(&attrs, "macro_export"),
allow_internal_unstable: attr::contains_name(&attrs, "allow_internal_unstable"),
attrs: attrs,
});

// macro_rules! has a side effect but expands to nothing.
fld.cx.bt_pop();
None
}
_ => {
fld.cx.span_err(
pth.span,
&format!("'{}' is not a tt-style macro",
extname));

MultiDecorator(..) | MultiModifier(..) => {
fld.cx.span_err(path.span,
&format!("`{}` can only be used in attributes", extname));
None
}
}
}

let opt_expanded = T::make_with(match mac_result(&path, ident, tts, mark, attrs, span, fld) {
Some(result) => result,
None => return T::dummy(span),
});

let expanded = if let Some(expanded) = opt_expanded {
expanded
} else {
let msg = format!("non-{kind} macro in {kind} position: {name}",
name = path.segments[0].identifier.name, kind = T::kind_name());
fld.cx.span_err(path.span, &msg);
return T::dummy(span);
};

let marked = expanded.fold_with(&mut Marker { mark: mark });
let fully_expanded = marked.fold_with(fld);
fld.cx.bt_pop();
fully_expanded
}

/// Rename loop label and expand its loop body
@@ -367,141 +424,6 @@ fn contains_macro_use(fld: &mut MacroExpander, attrs: &[ast::Attribute]) -> bool
false
}

// Support for item-position macro invocations, exactly the same
// logic as for expression-position macro invocations.
pub fn expand_item_mac(it: P<ast::Item>,
fld: &mut MacroExpander) -> SmallVector<P<ast::Item>> {
let (extname, path_span, tts, span, attrs, ident) = it.and_then(|it| match it.node {
ItemKind::Mac(codemap::Spanned { node: Mac_ { path, tts, .. }, .. }) =>
(path.segments[0].identifier.name, path.span, tts, it.span, it.attrs, it.ident),
_ => fld.cx.span_bug(it.span, "invalid item macro invocation")
});

check_attributes(&attrs, fld);

let fm = fresh_mark();
let items = {
let expanded = match fld.cx.syntax_env.find(extname) {
None => {
fld.cx.span_err(path_span,
&format!("macro undefined: '{}!'",
extname));
// let compilation continue
return SmallVector::zero();
}

Some(rc) => match *rc {
NormalTT(ref expander, tt_span, allow_internal_unstable) => {
if ident.name != keywords::Invalid.name() {
fld.cx
.span_err(path_span,
&format!("macro {}! expects no ident argument, given '{}'",
extname,
ident));
return SmallVector::zero();
}
fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
format: MacroBang(extname),
span: tt_span,
allow_internal_unstable: allow_internal_unstable,
}
});
// mark before expansion:
let marked_before = mark_tts(&tts[..], fm);
expander.expand(fld.cx, span, &marked_before[..])
}
IdentTT(ref expander, tt_span, allow_internal_unstable) => {
if ident.name == keywords::Invalid.name() {
fld.cx.span_err(path_span,
&format!("macro {}! expects an ident argument",
extname));
return SmallVector::zero();
}
fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
format: MacroBang(extname),
span: tt_span,
allow_internal_unstable: allow_internal_unstable,
}
});
// mark before expansion:
let marked_tts = mark_tts(&tts[..], fm);
expander.expand(fld.cx, span, ident, marked_tts)
}
MacroRulesTT => {
if ident.name == keywords::Invalid.name() {
fld.cx.span_err(path_span, "macro_rules! expects an ident argument");
return SmallVector::zero();
}

fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
format: MacroBang(extname),
span: None,
// `macro_rules!` doesn't directly allow
// unstable (this is orthogonal to whether
// the macro it creates allows it)
allow_internal_unstable: false,
}
});
// DON'T mark before expansion.

let allow_internal_unstable = attr::contains_name(&attrs,
"allow_internal_unstable");

let export = attr::contains_name(&attrs, "macro_export");
let def = ast::MacroDef {
ident: ident,
attrs: attrs,
id: ast::DUMMY_NODE_ID,
span: span,
imported_from: None,
export: export,
use_locally: true,
allow_internal_unstable: allow_internal_unstable,
body: tts,
};
fld.cx.insert_macro(def);

// macro_rules! has a side effect but expands to nothing.
fld.cx.bt_pop();
return SmallVector::zero();
}
_ => {
fld.cx.span_err(span,
&format!("{}! is not legal in item position",
extname));
return SmallVector::zero();
}
}
};

expanded.make_items()
};

let items = match items {
Some(items) => {
items.into_iter()
.map(|i| mark_item(i, fm))
.flat_map(|i| fld.fold_item(i).into_iter())
.collect()
}
None => {
fld.cx.span_err(path_span,
&format!("non-item macro in item position: {}",
extname));
return SmallVector::zero();
}
};

fld.cx.bt_pop();
items
}

/// Expand a stmt
fn expand_stmt(stmt: Stmt, fld: &mut MacroExpander) -> SmallVector<Stmt> {
// perform all pending renames
@@ -516,30 +438,8 @@ fn expand_stmt(stmt: Stmt, fld: &mut MacroExpander) -> SmallVector<Stmt> {
_ => return expand_non_macro_stmt(stmt, fld)
};

if let Some(ref attrs) = attrs {
check_attributes(attrs, fld);
}

// Assert that we drop any macro attributes on the floor here
drop(attrs);

let maybe_new_items =
expand_mac_invoc(mac.unwrap(), stmt.span,
|r| r.make_stmts(),
|stmts, mark| stmts.move_map(|m| mark_stmt(m, mark)),
fld);

let mut fully_expanded = match maybe_new_items {
Some(stmts) => {
// Keep going, outside-in.
let new_items = stmts.into_iter().flat_map(|s| {
fld.fold_stmt(s).into_iter()
}).collect();
fld.cx.bt_pop();
new_items
}
None => SmallVector::zero()
};
let mut fully_expanded: SmallVector<ast::Stmt> =
expand_mac_invoc(mac.unwrap(), None, attrs.into_attr_vec(), stmt.span, fld);

// If this is a macro invocation with a semicolon, then apply that
// semicolon to the final statement produced by expansion.
@@ -754,76 +654,10 @@ fn expand_pat(p: P<ast::Pat>, fld: &mut MacroExpander) -> P<ast::Pat> {
PatKind::Mac(_) => {}
_ => return noop_fold_pat(p, fld)
}
p.map(|ast::Pat {node, span, ..}| {
let (pth, tts) = match node {
PatKind::Mac(mac) => (mac.node.path, mac.node.tts),
p.and_then(|ast::Pat {node, span, ..}| {
match node {
PatKind::Mac(mac) => expand_mac_invoc(mac, None, Vec::new(), span, fld),
_ => unreachable!()
};
if pth.segments.len() > 1 {
fld.cx.span_err(pth.span, "expected macro name without module separators");
return DummyResult::raw_pat(span);
}
let extname = pth.segments[0].identifier.name;
let marked_after = match fld.cx.syntax_env.find(extname) {
None => {
fld.cx.span_err(pth.span,
&format!("macro undefined: '{}!'",
extname));
// let compilation continue
return DummyResult::raw_pat(span);
}

Some(rc) => match *rc {
NormalTT(ref expander, tt_span, allow_internal_unstable) => {
fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
format: MacroBang(extname),
span: tt_span,
allow_internal_unstable: allow_internal_unstable,
}
});

let fm = fresh_mark();
let marked_before = mark_tts(&tts[..], fm);
let mac_span = fld.cx.original_span();
let pat = expander.expand(fld.cx,
mac_span,
&marked_before[..]).make_pat();
let expanded = match pat {
Some(e) => e,
None => {
fld.cx.span_err(
pth.span,
&format!(
"non-pattern macro in pattern position: {}",
extname
)
);
return DummyResult::raw_pat(span);
}
};

// mark after:
mark_pat(expanded,fm)
}
_ => {
fld.cx.span_err(span,
&format!("{}! is not legal in pattern position",
extname));
return DummyResult::raw_pat(span);
}
}
};

let fully_expanded =
fld.fold_pat(marked_after).node.clone();
fld.cx.bt_pop();

ast::Pat {
id: ast::DUMMY_NODE_ID,
node: fully_expanded,
span: span
}
})
}
@@ -893,7 +727,13 @@ fn expand_annotatable(a: Annotatable,
let mut new_items: SmallVector<Annotatable> = match a {
Annotatable::Item(it) => match it.node {
ast::ItemKind::Mac(..) => {
expand_item_mac(it, fld).into_iter().map(|i| Annotatable::Item(i)).collect()
let new_items: SmallVector<P<ast::Item>> = it.and_then(|it| match it.node {
ItemKind::Mac(mac) =>
expand_mac_invoc(mac, Some(it.ident), it.attrs, it.span, fld),
_ => unreachable!(),
});

new_items.into_iter().map(|i| Annotatable::Item(i)).collect()
}
ast::ItemKind::Mod(_) | ast::ItemKind::ForeignMod(_) => {
let valid_ident =
@@ -1077,25 +917,7 @@ fn expand_impl_item(ii: ast::ImplItem, fld: &mut MacroExpander)
span: fld.new_span(ii.span)
}),
ast::ImplItemKind::Macro(mac) => {
check_attributes(&ii.attrs, fld);

let maybe_new_items =
expand_mac_invoc(mac, ii.span,
|r| r.make_impl_items(),
|meths, mark| meths.move_map(|m| mark_impl_item(m, mark)),
fld);

match maybe_new_items {
Some(impl_items) => {
// expand again if necessary
let new_items = impl_items.into_iter().flat_map(|ii| {
expand_impl_item(ii, fld).into_iter()
}).collect();
fld.cx.bt_pop();
new_items
}
None => SmallVector::zero()
}
expand_mac_invoc(mac, None, ii.attrs, ii.span, fld)
}
_ => fold::noop_fold_impl_item(ii, fld)
}
@@ -1139,25 +961,7 @@ pub fn expand_type(t: P<ast::Ty>, fld: &mut MacroExpander) -> P<ast::Ty> {
let t = match t.node.clone() {
ast::TyKind::Mac(mac) => {
if fld.cx.ecfg.features.unwrap().type_macros {
let expanded_ty = match expand_mac_invoc(mac, t.span,
|r| r.make_ty(),
mark_ty,
fld) {
Some(ty) => ty,
None => {
return DummyResult::raw_ty(t.span);
}
};

// Keep going, outside-in.
let fully_expanded = fld.fold_ty(expanded_ty);
fld.cx.bt_pop();

fully_expanded.map(|t| ast::Ty {
id: ast::DUMMY_NODE_ID,
node: t.node,
span: t.span,
})
expand_mac_invoc(mac, None, Vec::new(), t.span, fld)
} else {
feature_gate::emit_feature_err(
&fld.cx.parse_sess.span_diagnostic,
@@ -1426,38 +1230,6 @@ fn mark_tts(tts: &[TokenTree], m: Mrk) -> Vec<TokenTree> {
noop_fold_tts(tts, &mut Marker{mark:m})
}

// apply a given mark to the given expr. Used following the expansion of a macro.
fn mark_expr(expr: P<ast::Expr>, m: Mrk) -> P<ast::Expr> {
Marker{mark:m}.fold_expr(expr)
}

// apply a given mark to the given pattern. Used following the expansion of a macro.
fn mark_pat(pat: P<ast::Pat>, m: Mrk) -> P<ast::Pat> {
Marker{mark:m}.fold_pat(pat)
}

// apply a given mark to the given stmt. Used following the expansion of a macro.
fn mark_stmt(stmt: ast::Stmt, m: Mrk) -> ast::Stmt {
Marker{mark:m}.fold_stmt(stmt)
.expect_one("marking a stmt didn't return exactly one stmt")
}

// apply a given mark to the given item. Used following the expansion of a macro.
fn mark_item(expr: P<ast::Item>, m: Mrk) -> P<ast::Item> {
Marker{mark:m}.fold_item(expr)
.expect_one("marking an item didn't return exactly one item")
}

// apply a given mark to the given item. Used following the expansion of a macro.
fn mark_impl_item(ii: ast::ImplItem, m: Mrk) -> ast::ImplItem {
Marker{mark:m}.fold_impl_item(ii)
.expect_one("marking an impl item didn't return exactly one impl item")
}

fn mark_ty(ty: P<ast::Ty>, m: Mrk) -> P<ast::Ty> {
Marker { mark: m }.fold_ty(ty)
}

/// Check that there are no macro invocations left in the AST:
pub fn check_for_macros(sess: &parse::ParseSess, krate: &ast::Crate) {
visit::walk_crate(&mut MacroExterminator{sess:sess}, krate);
3 changes: 3 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
@@ -997,6 +997,9 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
ast::ExprKind::Try(..) => {
gate_feature_post!(&self, question_mark, e.span, "the `?` operator is not stable");
}
ast::ExprKind::InPlace(..) => {
gate_feature_post!(&self, placement_in_syntax, e.span, EXPLAIN_PLACEMENT_IN);
}
_ => {}
}
visit::walk_expr(self, e);
7 changes: 5 additions & 2 deletions src/test/compile-fail/macro-error.rs
Original file line number Diff line number Diff line change
@@ -8,12 +8,15 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Check that we report errors at macro definition, not expansion.
#![feature(type_macros)]

macro_rules! foo {
($a:expr) => $a; //~ ERROR macro rhs must be delimited
}

fn main() {
foo!(0);
foo!(0); // Check that we report errors at macro definition, not expansion.

let _: cfg!(foo) = (); //~ ERROR non-type macro in type position
derive!(); //~ ERROR `derive` can only be used in attributes
}