Skip to content

Commit f920008

Browse files
author
mejrs
committed
Improve proc macro attribute diagnostics
1 parent ef4046e commit f920008

15 files changed

+488
-22
lines changed

compiler/rustc_error_messages/locales/en-US/passes.ftl

+21
Original file line numberDiff line numberDiff line change
@@ -707,3 +707,24 @@ passes_ignored_derived_impls =
707707
[one] trait {$trait_list}, but this is
708708
*[other] traits {$trait_list}, but these are
709709
} intentionally ignored during dead code analysis
710+
711+
passes_proc_macro_typeerror = mismatched {$kind} signature
712+
.label = found {$found}, expected type `proc_macro::TokenStream`
713+
.note = {$kind}s must have a signature of `{$expected_signature}`
714+
715+
passes_proc_macro_diff_arg_count = mismatched {$kind} signature
716+
.label = found unexpected {$count ->
717+
[one] argument
718+
*[other] arguments
719+
}
720+
.note = {$kind}s must have a signature of `{$expected_signature}`
721+
722+
passes_proc_macro_missing_args = mismatched {$kind} signature
723+
.label = {$kind} must have {$expected_input_count ->
724+
[one] one argument
725+
*[other] two arguments
726+
} of type `proc_macro::TokenStream`
727+
728+
passes_proc_macro_invalid_abi = proc macro functions may not be `extern "{$abi}"`
729+
730+
passes_proc_macro_unsafe = proc macro functions may not be `unsafe`

compiler/rustc_passes/src/check_attr.rs

+124-6
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
77
use crate::errors::{
88
self, AttrApplication, DebugVisualizerUnreadable, InvalidAttrAtCrateLevel, ObjectLifetimeErr,
9-
OnlyHasEffectOn, TransparentIncompatible, UnrecognizedReprHint,
9+
OnlyHasEffectOn, ProcMacroDiffArguments, ProcMacroInvalidAbi, ProcMacroMissingArguments,
10+
ProcMacroTypeError, ProcMacroUnsafe, TransparentIncompatible, UnrecognizedReprHint,
1011
};
1112
use rustc_ast::{ast, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem};
1213
use rustc_data_structures::fx::FxHashMap;
13-
use rustc_errors::{fluent, Applicability, MultiSpan};
14+
use rustc_errors::{fluent, Applicability, IntoDiagnosticArg, MultiSpan};
1415
use rustc_expand::base::resolve_path;
1516
use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
1617
use rustc_hir as hir;
@@ -19,18 +20,19 @@ use rustc_hir::intravisit::{self, Visitor};
1920
use rustc_hir::{
2021
self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID, CRATE_OWNER_ID,
2122
};
22-
use rustc_hir::{MethodKind, Target};
23+
use rustc_hir::{MethodKind, Target, Unsafety};
2324
use rustc_middle::hir::nested_filter;
2425
use rustc_middle::middle::resolve_lifetime::ObjectLifetimeDefault;
2526
use rustc_middle::ty::query::Providers;
26-
use rustc_middle::ty::TyCtxt;
27+
use rustc_middle::ty::{ParamEnv, TyCtxt};
2728
use rustc_session::lint::builtin::{
2829
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES,
2930
};
3031
use rustc_session::parse::feature_err;
3132
use rustc_span::symbol::{kw, sym, Symbol};
3233
use rustc_span::{Span, DUMMY_SP};
3334
use rustc_target::spec::abi::Abi;
35+
use std::cell::Cell;
3436
use std::collections::hash_map::Entry;
3537

3638
pub(crate) fn target_from_impl_item<'tcx>(
@@ -62,8 +64,27 @@ enum ItemLike<'tcx> {
6264
ForeignItem,
6365
}
6466

67+
#[derive(Copy, Clone)]
68+
pub(crate) enum ProcMacroKind {
69+
FunctionLike,
70+
Derive,
71+
Attribute,
72+
}
73+
74+
impl IntoDiagnosticArg for ProcMacroKind {
75+
fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
76+
match self {
77+
ProcMacroKind::Attribute => "attribute proc macro",
78+
ProcMacroKind::Derive => "derive proc macro",
79+
ProcMacroKind::FunctionLike => "function-like proc macro",
80+
}
81+
.into_diagnostic_arg()
82+
}
83+
}
84+
6585
struct CheckAttrVisitor<'tcx> {
6686
tcx: TyCtxt<'tcx>,
87+
abort: Cell<bool>,
6788
}
6889

6990
impl CheckAttrVisitor<'_> {
@@ -172,7 +193,7 @@ impl CheckAttrVisitor<'_> {
172193
sym::path => self.check_generic_attr(hir_id, attr, target, Target::Mod),
173194
sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target),
174195
sym::macro_export => self.check_macro_export(hir_id, attr, target),
175-
sym::ignore | sym::should_panic | sym::proc_macro_derive => {
196+
sym::ignore | sym::should_panic => {
176197
self.check_generic_attr(hir_id, attr, target, Target::Fn)
177198
}
178199
sym::automatically_derived => {
@@ -182,6 +203,16 @@ impl CheckAttrVisitor<'_> {
182203
self.check_generic_attr(hir_id, attr, target, Target::Mod)
183204
}
184205
sym::rustc_object_lifetime_default => self.check_object_lifetime_default(hir_id),
206+
sym::proc_macro => {
207+
self.check_proc_macro(hir_id, target, ProcMacroKind::FunctionLike)
208+
}
209+
sym::proc_macro_attribute => {
210+
self.check_proc_macro(hir_id, target, ProcMacroKind::Attribute);
211+
}
212+
sym::proc_macro_derive => {
213+
self.check_generic_attr(hir_id, attr, target, Target::Fn);
214+
self.check_proc_macro(hir_id, target, ProcMacroKind::Derive)
215+
}
185216
_ => {}
186217
}
187218

@@ -2052,6 +2083,90 @@ impl CheckAttrVisitor<'_> {
20522083
errors::Unused { attr_span: attr.span, note },
20532084
);
20542085
}
2086+
2087+
fn check_proc_macro(&self, hir_id: HirId, target: Target, kind: ProcMacroKind) {
2088+
let expected_input_count = match kind {
2089+
ProcMacroKind::Attribute => 2,
2090+
ProcMacroKind::Derive | ProcMacroKind::FunctionLike => 1,
2091+
};
2092+
2093+
let expected_signature = match kind {
2094+
ProcMacroKind::Attribute => "fn(TokenStream, TokenStream) -> TokenStream",
2095+
ProcMacroKind::Derive | ProcMacroKind::FunctionLike => "fn(TokenStream) -> TokenStream",
2096+
};
2097+
2098+
let tcx = self.tcx;
2099+
if target == Target::Fn {
2100+
let Some(tokenstream) = tcx.get_diagnostic_item(sym::TokenStream) else {return};
2101+
let tokenstream = tcx.type_of(tokenstream);
2102+
2103+
let id = hir_id.expect_owner();
2104+
let hir_sig = tcx.hir().fn_sig_by_hir_id(hir_id).unwrap();
2105+
2106+
let sig = tcx.fn_sig(id);
2107+
2108+
if sig.abi() != Abi::Rust {
2109+
tcx.sess
2110+
.emit_err(ProcMacroInvalidAbi { span: hir_sig.span, abi: sig.abi().name() });
2111+
self.abort.set(true);
2112+
}
2113+
2114+
if sig.unsafety() == Unsafety::Unsafe {
2115+
tcx.sess.emit_err(ProcMacroUnsafe { span: hir_sig.span });
2116+
self.abort.set(true);
2117+
}
2118+
2119+
let output = sig.output().skip_binder();
2120+
2121+
// Typecheck the output
2122+
if tcx.normalize_erasing_regions(ParamEnv::empty(), output) != tokenstream {
2123+
tcx.sess.emit_err(ProcMacroTypeError {
2124+
span: hir_sig.decl.output.span(),
2125+
found: output,
2126+
kind,
2127+
expected_signature,
2128+
});
2129+
self.abort.set(true);
2130+
}
2131+
2132+
// Typecheck "expected_input_count" inputs, emitting
2133+
// `ProcMacroMissingArguments` if there are not enough.
2134+
if let Some(args) = sig.inputs().skip_binder().get(0..expected_input_count) {
2135+
for (arg, input) in args.iter().zip(hir_sig.decl.inputs) {
2136+
if tcx.normalize_erasing_regions(ParamEnv::empty(), *arg) != tokenstream {
2137+
tcx.sess.emit_err(ProcMacroTypeError {
2138+
span: input.span,
2139+
found: *arg,
2140+
kind,
2141+
expected_signature,
2142+
});
2143+
self.abort.set(true);
2144+
}
2145+
}
2146+
} else {
2147+
tcx.sess.emit_err(ProcMacroMissingArguments {
2148+
expected_input_count,
2149+
span: hir_sig.span,
2150+
kind,
2151+
expected_signature,
2152+
});
2153+
self.abort.set(true);
2154+
}
2155+
2156+
// Check that there are not too many arguments
2157+
let body_id = tcx.hir().body_owned_by(id.def_id);
2158+
let excess = tcx.hir().body(body_id).params.get(expected_input_count..);
2159+
if let Some(excess @ [begin @ end] | excess @ [begin, .., end]) = excess {
2160+
tcx.sess.emit_err(ProcMacroDiffArguments {
2161+
span: begin.span.to(end.span),
2162+
count: excess.len(),
2163+
kind,
2164+
expected_signature,
2165+
});
2166+
self.abort.set(true);
2167+
}
2168+
}
2169+
}
20552170
}
20562171

20572172
impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
@@ -2214,12 +2329,15 @@ fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>)
22142329
}
22152330

22162331
fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
2217-
let check_attr_visitor = &mut CheckAttrVisitor { tcx };
2332+
let check_attr_visitor = &mut CheckAttrVisitor { tcx, abort: Cell::new(false) };
22182333
tcx.hir().visit_item_likes_in_module(module_def_id, check_attr_visitor);
22192334
if module_def_id.is_top_level_module() {
22202335
check_attr_visitor.check_attributes(CRATE_HIR_ID, DUMMY_SP, Target::Mod, None);
22212336
check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs());
22222337
}
2338+
if check_attr_visitor.abort.get() {
2339+
tcx.sess.abort_if_errors()
2340+
}
22232341
}
22242342

22252343
pub(crate) fn provide(providers: &mut Providers) {

compiler/rustc_passes/src/errors.rs

+50
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
1212
use rustc_middle::ty::{MainDefinition, Ty};
1313
use rustc_span::{Span, Symbol, DUMMY_SP};
1414

15+
use crate::check_attr::ProcMacroKind;
1516
use crate::lang_items::Duplicate;
1617

1718
#[derive(LintDiagnostic)]
@@ -1508,3 +1509,52 @@ pub struct ChangeFieldsToBeOfUnitType {
15081509
#[suggestion_part(code = "()")]
15091510
pub spans: Vec<Span>,
15101511
}
1512+
1513+
#[derive(Diagnostic)]
1514+
#[diag(passes_proc_macro_typeerror)]
1515+
#[note]
1516+
pub(crate) struct ProcMacroTypeError<'tcx> {
1517+
#[primary_span]
1518+
#[label]
1519+
pub span: Span,
1520+
pub found: Ty<'tcx>,
1521+
pub kind: ProcMacroKind,
1522+
pub expected_signature: &'static str,
1523+
}
1524+
1525+
#[derive(Diagnostic)]
1526+
#[diag(passes_proc_macro_diff_arg_count)]
1527+
pub(crate) struct ProcMacroDiffArguments {
1528+
#[primary_span]
1529+
#[label]
1530+
pub span: Span,
1531+
pub count: usize,
1532+
pub kind: ProcMacroKind,
1533+
pub expected_signature: &'static str,
1534+
}
1535+
1536+
#[derive(Diagnostic)]
1537+
#[diag(passes_proc_macro_missing_args)]
1538+
pub(crate) struct ProcMacroMissingArguments {
1539+
#[primary_span]
1540+
#[label]
1541+
pub span: Span,
1542+
pub expected_input_count: usize,
1543+
pub kind: ProcMacroKind,
1544+
pub expected_signature: &'static str,
1545+
}
1546+
1547+
#[derive(Diagnostic)]
1548+
#[diag(passes_proc_macro_invalid_abi)]
1549+
pub(crate) struct ProcMacroInvalidAbi {
1550+
#[primary_span]
1551+
pub span: Span,
1552+
pub abi: &'static str,
1553+
}
1554+
1555+
#[derive(Diagnostic)]
1556+
#[diag(passes_proc_macro_unsafe)]
1557+
pub(crate) struct ProcMacroUnsafe {
1558+
#[primary_span]
1559+
pub span: Span,
1560+
}

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ symbols! {
287287
Target,
288288
ToOwned,
289289
ToString,
290+
TokenStream,
290291
Try,
291292
TryCaptureGeneric,
292293
TryCapturePrintable,

library/proc_macro/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ pub fn is_available() -> bool {
7474
///
7575
/// This is both the input and output of `#[proc_macro]`, `#[proc_macro_attribute]`
7676
/// and `#[proc_macro_derive]` definitions.
77+
#[rustc_diagnostic_item = "TokenStream"]
7778
#[stable(feature = "proc_macro_lib", since = "1.15.0")]
7879
#[derive(Clone)]
7980
pub struct TokenStream(Option<bridge::client::TokenStream>);

tests/ui/proc-macro/proc-macro-abi.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![crate_type = "proc-macro"]
2+
#![allow(warnings)]
3+
4+
extern crate proc_macro;
5+
use proc_macro::TokenStream;
6+
7+
#[proc_macro]
8+
pub extern "C" fn abi(a: TokenStream) -> TokenStream {
9+
//~^ ERROR proc macro functions may not be `extern
10+
a
11+
}
12+
13+
#[proc_macro]
14+
pub extern "system" fn abi2(a: TokenStream) -> TokenStream {
15+
//~^ ERROR proc macro functions may not be `extern
16+
a
17+
}
18+
19+
#[proc_macro]
20+
pub extern fn abi3(a: TokenStream) -> TokenStream {
21+
//~^ ERROR proc macro functions may not be `extern
22+
a
23+
}
24+
25+
#[proc_macro]
26+
pub extern "Rust" fn abi4(a: TokenStream) -> TokenStream {
27+
a
28+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: proc macro functions may not be `extern "C"`
2+
--> $DIR/proc-macro-abi.rs:8:1
3+
|
4+
LL | pub extern "C" fn abi(a: TokenStream) -> TokenStream {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: proc macro functions may not be `extern "system"`
8+
--> $DIR/proc-macro-abi.rs:14:1
9+
|
10+
LL | pub extern "system" fn abi2(a: TokenStream) -> TokenStream {
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: proc macro functions may not be `extern "C"`
14+
--> $DIR/proc-macro-abi.rs:20:1
15+
|
16+
LL | pub extern fn abi3(a: TokenStream) -> TokenStream {
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
19+
error: aborting due to 3 previous errors
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![crate_type = "proc-macro"]
2+
3+
extern crate proc_macro;
4+
use proc_macro::TokenStream;
5+
6+
#[proc_macro_attribute]
7+
pub fn bad_input(input: String) -> TokenStream {
8+
//~^ ERROR mismatched attribute proc macro signature
9+
::proc_macro::TokenStream::new()
10+
}
11+
12+
#[proc_macro_attribute]
13+
pub fn bad_output(input: TokenStream) -> String {
14+
//~^ ERROR mismatched attribute proc macro signature
15+
//~| ERROR mismatched attribute proc macro signature
16+
String::from("blah")
17+
}
18+
19+
#[proc_macro_attribute]
20+
pub fn bad_everything(input: String) -> String {
21+
//~^ ERROR mismatched attribute proc macro signature
22+
//~| ERROR mismatched attribute proc macro signature
23+
input
24+
}
25+
26+
#[proc_macro_attribute]
27+
pub fn too_many(a: TokenStream, b: TokenStream, c: String) -> TokenStream {
28+
//~^ ERROR mismatched attribute proc macro signature
29+
}

0 commit comments

Comments
 (0)